共计 4862 个字符,预计需要花费 13 分钟才能阅读完成。
前言
大家都晓得,面试犹如考试,在考试之前,老师个别都会先给咱们 理清常识脉络 , 整顿温习纲要 ,依照纲要精准学习, 疾速达到应试状态 。所以,一本精炼的温习纲要对面试尤为重要。本 武林秘笈 从本周开始,每周会更新 3-5 个知识点,尽量的精简与提炼常识脉络,极为适宜利用碎片工夫来学习。心愿你和我一起,每周提高一点点~????
1.JS 的数据类型及存储形式的区别
在 ECMAScript 标准中,共定义了 8 种数据类型,分为 根本数据类型 和援用数据类型(援用数据类型又称简单数据类型)两大类:
根本数据类型:String、Number、Boolean、Null、Undefined、Symbol(ES6 新增)、BigInt(ES10 新增)
援用数据类型:Object(Function、Array、Date、RegExp、Math… 都是 Object 类型的实例对象)
贮存形式的区别:
根本数据类型:变量名和值都贮存在栈内存中;
援用数据类型 :变量名贮存在 栈内存 中,值贮存在 堆内存 中,堆内存中会提供一个援用地址指向堆内存中的值,而这个援用地址是贮存在栈内存中的。
2. 罕用的判断 JS 数据类型的四种办法
typeof
毛病:不能判断 Object 类型,也不能判断 Null 类型。
type null
返回后果为object
是因为 JavaScript 是用 32 位比特来存储值的,且是通过值的低 1 位或 3 位来辨认类型的,object 的前三位示意是 000,null 为机器码空指针,32 位示意全是 0,它们俩的前三位一样,所以typeof null
也会打印出object
。
console.log(typeof "") // string
console.log(typeof 1) // number
console.log(typeof true) // boolean
console.log(typeof Symbol()) // symbol
console.log(typeof undefined) // undefined
console.log(typeof null) // object
console.log(typeof []) // object
console.log(typeof {}) // object
constructor
constructor
能够找到这个变量是通过谁结构进去的。
毛病:
- 不能判断 null 和 undefined,因为 null 和 undefined 是有效的对象,因而是不会有 constructor 存在,这两种类型的数据须要通过其余形式来判断。
- 函数的 constructor 是不稳固的,这个次要体现在自定义对象上,当开发者重写 prototype 后,原有的 constructor 援用会失落,constructor 会默认为 Object
console.log(("").constructor == String) // true
console.log((1).constructor == Number) // true
console.log((true).constructor == Boolean); // true
console.log([].constructor == Array) // true
console.log(({}).constructor == Object) // true
instanceof
instanceof
用来判断 A 是否是 B 的实例,在这里须要特地留神的是:instanceof 检测的是原型。
毛病:instanceof 只能用来判断两个对象是否属于实例关系,而不能判断一个对象实例具体属于哪种类型。
console.log([] instanceof Array) // true
console.log({} instanceof Object) // true
console.log([] instanceof Object) // true
console.log(new Date() instanceof Date) // true
Object.prototype.toString.call()
通过 .call 将传进来的对象作为 Object 原型上的 this,而后通过 toString 办法能够看到具体的类型。
优缺点:不能细分谁是谁的实例,然而判断这个变量的类型还是十分不便和靠谱的。
console.log(Object.prototype.toString.call()) // [object Undefined]
console.log(Object.prototype.toString.call("")) // [object String]
console.log(Object.prototype.toString.call(1)) // [object Number]
console.log(Object.prototype.toString.call(true)) // [object Boolean]
console.log(Object.prototype.toString.call(Symbol())) // [object Symbol]
console.log(Object.prototype.toString.call(null)) // [object Null]
console.log(Object.prototype.toString.call(new Function())) // [object Function]
console.log(Object.prototype.toString.call(new Date())) // [object Date]
console.log(Object.prototype.toString.call([])) // [object Array]
console.log(Object.prototype.toString.call(new RegExp())) // [object RegExp]
console.log(Object.prototype.toString.call(new Error())) // [object Error]
console.log(Object.prototype.toString.call(document)) // [object HTMLDocument]
console.log(Object.prototype.toString.call(window)) // [object Window]
3. 作用域、作用域链、执行上下文(EC)、以执行上下文栈(ECS)
作用域
函数在定义时会产生两种 作用域
:分为 全局作用域 和 部分作用域,所以 JS 的作用域是 动态作用域
。
执行上下文(EC)
当 JS 在执行一段代码的时候,会产生 执行上下文(EC)
;
- 「执行上下文」一共有三种类型:全局执行上下文、函数执行上下文、
eval
执行上下文。 - 「执行上下文」蕴含三个局部:变量对象 (VO)、作用域链(词法作用域)、
this
指向。
流动对象 (AO): 当变量对象所处的上下文为 active EC 时,称为流动对象。
执行上下文栈(ECS)
在「执行上下文」的造成过程中,会应用「执行上下文栈」来治理执行上下文。
代码执行的过程:
- 创立 全局上下文 (global EC)
- 全局执行上下文 (caller) 逐行 自上而下 执行。遇到函数时,函数执行上下文 (callee) 被 push 到执行栈顶层
- 函数执行上下文被激活,成为 active EC, 开始执行函数中的代码,caller 被挂起
- 函数执行完后,callee 被 pop 移除出执行栈,控制权交还全局上下文 (caller),继续执行
例子:
function a() {b();
}
function b() {c();
}
function c() {console.log("welcome");
}
/**
ECS = [globalContext]
ECS.push(functionAContext);
ECS.push(functionBContext);
ECS.push(functionCContext);
ECS.pop();
ECS.pop();
ECS.pop();
**/
作用域链
函数外部会保留一个 [[scope]]
属性,它会保留所有的父级的变量对象。而且在函数执行的时候,会把本身的 AO
对象加进去,所以执行的时候会先找本人的 AO
属性,找不到的话会向上查找,这就是作用域链。
「作用域链」有两局部组成:
[[scope]]
属性: 指向父级变量对象和作用域链,也就是蕴含了父级的[[scope]]
和AO
。AO
: 本身流动对象
如此 [[scope]]
蕴含[[scope]]
,便自上而下造成一条「作用域链」。
例子:
var a = 1;
function sum() {
var b = 2;
return a+b;
}
sum();
/*
sum.[[scope]] = {globalContext.VO}
// 编译阶段:sumContext = {
A0:{
arguments: {length: 0},
b: undefined
},
Scope: [A0, sum.[[scope]]]
}
// 执行阶段:ESC = [
globalContext,
sumContext
]
A0:{
arguments: {length: 0},
b: 2
},
// 执行完:ECS.pop()
*/
4. 原型与原型链
- 原型:
prototype
,每一个函数都有一个prototype
属性; - 原型链:
__proto__
,每一个对象都有一个__proto__属性; - 构造函数:能够通过
new
来新建一个对象 的函数。 - 实例:通过构造函数和
new
创立进去的对象,便是实例。实例通过__proto__
指向原型,通过constructor
指向构造函数。
例子:
function Animal() {this.type = "哺乳类"}
Animal.prototype.type = "哺乳"
// 实例
let animal = new Animal();
console.log(animal.__proto__.__proto__ === Object.prototype); //true
console.log(Animal.prototype.constructor == Animal); // true
console.log(Object.prototype.__proto__); // null
这么说可能比拟形象,我画了一张图帮大家彻底了解他们之间的关系:
非凡的状况:Function 能够充当对象,也能够充当函数
console.log(Function.__proto__ === Function.prototype);
console.log(Object.__proto__ === Function.prototype);
console.log(Object.__proto__ === Function.__proto__);
5. 谈谈你对闭包的了解
闭包属于一种非凡的作用域。它的定义能够了解为: 父函数被销毁 的状况下,返回出的子函数的 [[scope]]
中依然保留着父级的单变量对象和作用域链,因而能够持续拜访到父级的变量对象,这样的函数称为闭包。一句话解释就是:函数定义的作用域和函数执行的作用域,不在同一个作用域下。
- 闭包会产生一个很经典的问题:
多个子函数的 [[scope]]
都是同时指向父级,是齐全共享的。因而当父级的变量对象被批改时,所有子函数都受到影响。
- 解决:
- 变量能够通过
函数参数的模式
传入,防止应用默认的[[scope]]
向上查找 - 应用
setTimeout
包裹,通过第三个参数传入 - 应用
块级作用域
,让变量成为本人上下文的属性,防止共享
- 手写一个简略的闭包
上面例子中的 closure 就是一个闭包:
function func(){
var a = 1,b = 2;
function closure(){return a+b;}
return closure;
}
对于
作者齐小神,前端程序媛一枚。
有点文艺,喜爱摄影。尽管当初朝九晚五,埋头苦学,但幻想是做女侠,扶贫济穷,仗剑走咫尺。心愿有一天能改完 BUG 去实现本人的幻想。
公众号:大前端 Space,不定时更新,欢送来玩~