this/闭包/作⽤域
专业术语
- 常量、变量、数据类型
- 形参、实参
- 匿名函数、具名函数、自执行函数
- 函数声明、函数表达式
- 堆、栈
- 同步、异步、进程、线程
执行上下文
当函数执行时,会创建一个称为执行上下文(execution context)的环境,分为创建和执行两个阶段
创建阶段
创建阶段,指函数调用但未执行任何代码时,此时创建了一个拥有三个属性的对象:
executionContext = {
scopeChain: {}, // 创建作用域链(scope chain)
variableObject: {}, // 初始化变量、函数、形参
this: {} // 指定this
}
代码执行阶段
代码执行阶段主要的工作是:1、分配变量、函数的引用,赋值。2、执行代码。
// 执行这段代码
function demo(num) {
var name = 'jinxing';
var fn = function fn() {};
function a() {}
}
demo(500);
// 创建阶段,在这个阶段就出现了变量提升(Hoisting)
executionContext = {
scopeChain: { ... }, // 作用域链是一个引用数组
variableObject: {
arguments: { // 创建了参数对象
0: 100,
length: 1
},
num: 500, // 创建形参名称,赋值/或创建引用拷贝
a: pointer to function a(), // 有内部声明的话,创建引用指向函数体
name: undefined, // 有内部声明变量a,初始化为undefined
fn: undefined // 有内部声明变量fn,初始化为undefined
},
this: { ... }
}
// 代码执行阶段,在这个阶段主要是赋值并执行代码
executionContext = {
scopeChain: { ... },
variableObject: {
arguments: {
0: 100,
length: 1
},
num: 500,
a: pointer to function a(),
name: 'jinxing', // 分配变量,赋值
fn: pointer to function fn() // 分配函数的引用,赋值
},
this: { ... }
}
执行上下文栈
- 浏览器中的JS解释器是单线程的,相当于浏览器中同一时间只能做一个事情。
- 代码中只有一个全局执行上下文,和无数个函数执行上下文,这些组成了执行上下文栈(Execution Stack)。
- 一个函数的执行上下文,在函数执行完毕后,会被移出执行上下文栈。
作用域链
作用域链是一个引用数组,当一个块或函数嵌套在另一个块或函数中时,就发生了作用域的嵌套。在当前函数中如果js引擎无法找到某个变量,就会往上一级嵌套的作用域中去寻找,知道找到该变量或抵达全局作用域,这样的链式关系就称为作用域链(Scope Chain)。js 是基于词法作用域的,是静态分析的,并不是执行的时候向上找,而是定义的时候向上找。
闭包
闭包是指有权访问另个一个函数作用域中变量的函数。
// 举例:
function create() {
var a = 0
return function () {
var c = 1
console.log(a)
}
}
var b = create()
b()
// create函数执行,创建了自己的执行上下文环境和作用域链,create的作用域链数组中,0号位放的是create函数自己的活动变量(对象){a: 0}的引用,1号位放的是全局变量对象(window)的引用。
// 当create函数执行完毕,返回一个新的匿名函数赋值给b,此时create函数的执行上下文环境的作用域链会被销毁,但是b函数的作用域链中还存在对create函数活动变量(变量)的引用,所以内存中的{a: 0}不会被销毁。
// b函数执行,创建自己的执行上下文环境及相应的作用域链,b函数作用域链的0号位是自己的活动变量(对象){c: 1}的引用,1号位是create函数的活动变量(对象){a: 0}的引用,2号位放的是全局变量对象(window)的引用。
// b函数执行完毕,销毁b函数的执行上下文环境和作用域链。
this
谁用点操作符调用函数,函数内的this就指向谁。
// 例 1: 函数直接调⽤时
function myfunc() {
console.log(this) // this是widow
}
myfunc()
// 例 2:函数被别⼈调⽤时
function myfunc() {
console.log(this) // this是对象a
}
var a = {
myfunc: myfunc
}
a.myfunc()
// 例 3:new⼀个实例时
function Person(name) {
this.name = name;
console.log(this); // this是指实例p
}
var p = new Person('jinxing');
// 例 4:有apply、call、bind时
function getColor(color) {
this.color = color;
console.log(this);
}
function car(name, color) {
this.name = name; // this指的是实例car
getColor.call(this, color); // 这⾥的this从原本的getColor,变成了car
}
var car = new Car('卡⻋', '绿⾊');
// 例 5:有箭头函数时
var a = {
myfunc: function() {
setTimeout(() => {
console.log(this); // this指向父级作用域中的this,也就是a
}, 0)
}
};
a.myfunc();