近期面一下前端,整理一下自己对一些知识点的理解。

每个人都知道(逃),JavaScript 有两类数据类型:基本 和 引用 数据类型。

基本类型数据保存在在内存中;引用类型数据保存在内存中,而它的变量是一个指向堆内存中实际对象的引用,存在中。所以只使用 = 赋值,它无法做到深层的拷贝。所以就有了深拷贝和浅拷贝的机制,区别在是否开拓一个新的,从而隔离拷贝出来的新对象。

对象、函数之类的数据就属于引用数据类型。

而原型链的机制和各种各样的设计,意味着 JS 当中对象和函数是不分家的。函数是 Function 对象的一个实例,可以作为参数、返回值,可以被赋值给变量,也可以被函数修改,返回一个新的函数(装饰器)…

作为存在面向对象特性的语言,this 在 JavaScript 当中也存在。this 关键字是函数运行时自动生成的一个内部对象,只能在函数内部使用,总指向调用它的对象。在函数执行过程中,this一旦被确定了,就不可以再更改。

上下文、this、闭包

然而,this 的值不由对象决定,而由代码执行时调用它的上下文实时决定。。。

优先级的顺序是:(从高往低)

  • new 绑定(这个函数是构造函数,创建实例的时候 this 指向实例对象。let obj = new Test())
  • 显式绑定(func. apply()、bind()、call(),改变 func 的 this 的调用对象,参数就是改变后调用的对象。)
  • 隐式绑定(函数还可以作为某个对象的方法调用,其中的 this 指向这个上级对象)(函数执行上下文)
  • 默认绑定(仅非严格模式,绑定到全局对象,this 指向 window)(也就是 全局执行上下文,全局对象也就是 window 对象。)

而上面提到的执行上下文(全局 / 函数 / eval),是一个存在生命周期的概念,分为创建 - 执行 - 回收,

创建

为了确保它可以工作,首先要对三个要素进行创建:this 的绑定、词法环境、变量环境 这三个组件。

当函数被调用时,会创建一个新的执行上下文,并形成一个新的作用域链。这个作用域链包括了当前执行上下文的词法环境和外部环境引用,沿着作用域链,JavaScript 引擎可以找到变量和函数的定义。

正因此,代码执行的时候才调用上下文,正因为调用上下文的时候绑定,这个过程才变得非常抽象,可能是因为这个随处用 this 的方法太野史了(全贬义),默认绑定在严格模式下不可用。

而显然,代码的执行上下文是嵌套的,优先级遵循 LIFO 的结构,所以 执行上下文 使用执行栈来管理。

第一行代码执行,全局执行上下文,push

遇到函数,函数执行上下文,push…

执行

执行代码。源代码声明的位置无变量,分配 undefined。

回收

出栈,等待虚拟机回收。

一个例子

1
2
3
4
5
6
7
8
9
10
function makeUser() {
return {
name: "John",
ref: this
};
}

let user = makeUser();

alert( user.ref.name ); // 结果是什么?

TypeError: Cannot read properties of undefined (reading ‘name’)?

这个 this 被带出来的时候指向 window。

1
2
3
4
5
6
7
8
9
10
11
12
function makeUser() {
return {
name: "John",
ref() {
return this;
}
};
}

let user = makeUser();

alert( user.ref().name ); // John

设置 this 的规则不考虑对象定义。只有调用那一刻才重要。

换绑 —— 装饰器模式,apply,bind,call

所以,也可以把含有 this 的函数,通过装饰器(返回一个函数的函数,用于修改函数)的模式来修改。

apply, bind, call 就可以改变函数执行时的上下文,不过细节有点区别。

1
2
3
4
5
6
7
8
9
func.apply(context, [args]);
/*
apply 接受两个参数,第一个参数是 this 的指向,第二个参数是函数接受的参数,以数组的形式传入. 改变 this 指向后原函数会立即执行,且此方法只是临时改变 this 指向一次,将 func 的 this 变成 context。
*/
func.call(context, arg1, arg2, ...);
/* call 传入的是参数列表而已。改变 func 的指向一次。*/

const bindFunc = func.bind(context, arg1, arg2, ...);
// bind 会永久改变 bindFunc 的 this 指向为 context(返回一个 fn),而此函数其他地方与 func 并无二异。

和作用域的关系

当然,要区分两个概念就是作用域和执行上下文。作用域是由代码的语法、词法决定的,是静态的。作用域决定了执行上下文中的变量和函数的可见性和访问规则:当代码执行时,JavaScript 引擎会根据作用域链在执行上下文中查找变量和函数的定义。作用域链是由当前执行上下文的词法环境和外部环境引用组成的。