一、专业术语
- 常量、变量、数据类型
- 形参、实参
- 匿名函数、具名函数、自执行函数
- 函数声明、函数表达式
- 堆、栈
- 同步、异步、进程、线程
1. 执行上下文
当函数执行时,会创建一个称为执行上下文(execution contex)的环境,分为创建和执行2个阶段。
2. 创建阶段
创建阶段,指函数被调用但还未执行任何代码时,此时创建了一个拥有3个属性的对象:
executionContext = {
scopeChain: {}, // 创建作用域链(scope chain)
variableObject: {}, // 初始化变量、函数、形参
this: {} // 指定this
}
3. 代码执行阶段
代码执行阶段主要的工作是:
- 分配变量、函数的引用,赋值
- 执行代码
举个栗子
// 一段这样的代码
function demo(num) {
var name = 'xiaowa';
var getData = function getData() {};
function c() {}
}
demo(100);
// 创建阶段大致这样,在这个阶段就出现了【变量提升(Hoisting)】
executionContext = {
scopeChain: { ... },
variableObject: {
arguments: {
0: 100,
length: 1
},
num: 100, //
c: pointer to function c(), // 有内部函数声明的话,创建引用指向函数体
name: undefined, // 有内部声明变量a,初始化为undefined
getData: undefined // 有内部声明变量b,初始化为undefined
},
this: { ... }
}
// 代码执行阶段,在这个阶段主要是赋值并执行代码
executionContext = {
scopeChain: { ... },
variableObject: {
arguments: {
0: 100,
length: 1
},
num: 100,
c: pointer to function c(),
name: 'xiaowa', // 分配变量,赋值
getData: pointer to function getData() // 分配函数的引用,赋值
},
this: { ... }
}
4. 执行上下文栈
- 浏览器中的JS解释器是单线程的,相当于浏览器中同一时间只能做一个事情。
- 代码中只有一个全局执行上下文,和无数个函数执行上下文,这些组成了执行上下文栈 (Execution Stack)。
- 一个函数的执行上下文,在函数执行完毕后,会被移出执行上下文栈。
举个栗子
function c(){
console.log('ok');
}
function a(){
function b(){
c();
}
b();
}
a();
这个栗子的执行上下文栈是这样的
二、 作用域
js中有全局作用域、函数作用域,es6中又增加了块级作用域。作用域的最大用途就是隔离变量或函 数,并控制他们的生命周期。作用域是在函数执行上下文创建时定义好的,不是函数执行时定义的。
举个栗子
// 不要看晕了哦~
function a () {
return function b() {
var myname = 'b';
console.log(myname); // b
}
}
function c() {
var myname = 'c';
b();
}
var b = a();
c();
// 去掉函数b中的myname声明后
function a () {
return function b() {
// var myname = 'b';
console.log(myname); // 这里会报错
}
}
function c() {
var myname = 'c'; b();
}
var b = a();
c();
三、 作用域链
当一个块或函数嵌套在另一个块或函数中时,就发生了作用域的嵌套。在当前函数中如果js引擎无法找 到某个变量,就会往上一级嵌套的作用域中去寻找,直到找到该变量或抵达全局作用域,这样的链式 关系就称为作用域链(Scope Chain)。
作用域链只和
定义
有关 和 函数的执行调用栈没有关系。
四、 闭包
高级程序设计3 中:闭包是指有权访问另外一个函数作用域中的变量的函数,可以理解为(能够读取其他函数内部变量的函数)
function outer() {
var top = xxxx;
function inner() {
xxx.innerHTML = top;
}
}
-
闭包是指有权限访问另外一个函数作用域中的变量的函数。
-
闭包可以实现真正的私有变量。
-
闭包可以用于节流的处理。
-
闭包可以用于存在缓存的变量。(同一个函数中尽量返回相同类型的数据)
平时用在哪儿?
- 封装私有变量(amd的框架等都使用)
// 普通的定义类的方式 function Person() { this._attackVolume = 100; } Person.prototype = { attack(body) { body.bloodVolume -= this.attackVolume - body.defenseVolume; } }; var person = new Person(); console.log(person._attackVolume); // 工厂方法 function Person() { var _attackVolume = 100; return { attack() { body.bloodVolume -= this.attackVolume - body.defenseVolume; } }; } var person = new Person(); console.log(person._attackVolume);
- 存储变量
// 封装的时候 function getListDataManager() { // 外层scope中定义一个变量 let localData = null; return { getData() { // 里面的函数使用外层的变量,而且是反复使用 if (localData) { return Promise.resolve(localData); } return fetch('xxxx') .then(data => localData = data.json()); } }; } // 用的时候 const listDataManager = getListDataManager(); button.onclick = () => { // 每次都会去获取数据,但是有可能是获取的缓存的数据 text.innerHTML = listDataManager.getData(); }; window.onscroll = () => { // 每次都会去获取数据,但是有可能是获取的缓存的数据 text.innerHTML = listDataManager.getData(); };
五、 this
一共有5种场景
场景1: 函数直接调用时
function myfunc() {
console.log(this) // this是widow
}
var a = 1;
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('zhaowa');
场景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: 箭头函数时
// 复习一下场景1
var a = {
myfunc: function() {
setTimeout(function(){
console.log(this);
}, 0)
}
};
a.myfunc();
// 稍微改变一下
var a = {
myfunc: function() {
var that = this;
setTimeout(function(){
console.log(that);
}, 0)
}
};
a.myfunc();
// 箭头函数
var a = {
myfunc: function() {
setTimeout(() => {
console.log(this);
}, 0)
}
};
a.myfunc();
总结一下
- 对于直接调用的函数来说,不管函数被放在了什么地方,
this
都是window
- 对于被别人调用的函数来说,被谁点出来的,
this
就是谁 - 在构造函数中,类中(函数体中)出现的
this.xxx = xxx
中的this
是当前类的一个实例 call
、apply
时,this
是第一个参数。bind
要优与call
/apply
哦,call
参数多,apply
参数少- 箭头函数没有自己的
this
,需要看其外层的是否有函数,如果有,外层函数的this
就是内部箭头函数的this
,如果没有,则this
是window
;箭头函数中的this
指向定义函数的上一层作用域中的this
this 指向三板斧
- 自然执行时,就是
window
或undefined(严格模式)
- 执行时,被谁
点
就是谁(最近的) - 执行时,
new
的时候,是new
出来的对象
不要与作用域链搞混,
作用域链是找定义时
的,this 指向是找执行时
的
扩展
- 逗号表达式
var a = (1, 2);
,返回最后一个值,但this
是window
var a = (1, 2); // window (1, wife.buy)(); // window,这是立即执行函数
- bind > new > apply、call > methods > 默认
bind
: 不会立即执行,是返回的一个函数,不会改变原函数
apply
:会立即执行,参数是(xx, [ ])
call
:会立即执行,参数是(xx, arg1, arg2...)
口诀:
call
打电话一个给一个打
() => {} :箭头函数六亲不认,手动改变 this 指向无效