this 绑定
我们可以从以下两个方向理解 this 绑定的原则:
- 首先,从时间上来说,this 对象的赋值操作发生在编译阶段,也可以大致理解为发生在环境创建的时候,而非函数定义的时候。
- 其次,从空间上来说,内部函数在搜索 this 对象的值时,只会搜索到其执行环境为止,永远不可能直接访问外部函数中的 this 对象。
this 绑定规则
1、当函数不带任何修饰而直接进行调用时,this 默认指向全局对象。
例子:
function fn() {
console.log(this.name);
}
var name = "Tom";
fn();
//输出:Tom
如果使用严格模式,this 会绑定到 undefined,因此默认绑定全局对象的前提条件是,函数运行在非严格模式下。
例子:
function fn() {
"use strict";
console.log(this.name);
}
var name = "Tom";
fn();
//输出:报错
2、当函数作为对象的方法进行调用时,this 指向该对象。
例子:
var name = "out";
var obj = {
name: "int",
getName: function() {
console.log(this.name);
}
}
obj.getName();
//输出:int
无论是直接在 obj 中定义,还是先定义再添加为 obj 的属性值,这个函数( getName )严格来说都不属于 obj 对象。因此,下面这种情况,会造成函数丢失绑定的对象。
例子:
var fn = function() {
console.log(this.name);
}
var name = "out";
var obj = {
name: "int",
getName: fn
}
var a = obj.getName;
a();
//输出:out
*虽然看上去 a 是 obj.getName 的一个引用,但是实际上,它引用的是 fn 函数本身,此时的 a() 其实是一个不带任何修饰的函数调用,因此 this 默认指向全局对象。
另外,回调函数中丢失 this 原有的绑定也是非常常见的现象。
例子:
var fn = function() {
console.log(this.name);
}
var run = function(callback) {
callback();
}
var name = "out";
var obj = {
name: "int",
getName: fn
}
run(obj.getName);
//输出:out
*造成这种现象的原因和上面的例子相同,callback 引用的是 fn 函数本身。
3、使用 call() 和 apply() 方法直接指定 this 的绑定对象。
例子:
var name = "Amy";
var fn = function() {
console.log(this.name);
}
fn(); //输出:Amy
fn.call({
name: "Tom"
}); //输出:Tom
针对回调函数中丢失绑定的问题,我们可以使用 call() 和 apply() 方法,强制把 fn 的 this 绑定到 obj。这种绑定我们称之为“硬绑定”。
例子:
var fn = function() {
console.log(this.name);
}
var run = function(callback) {
callback.call(obj);
}
var name = "out";
var obj = {
name: "int",
getName: fn
}
run(obj.getName);
//输出:int
由于硬绑定是一种非常常用的模式,所以在 ES5 中提供了内置的方法 bind(),它的用法如下:
例子:
var fn = function() {
console.log(this.name);
}
var name = "out";
var obj = {
name: "int",
getName: fn
}
var a = fn.bind(obj);
a();
//输出:int
如果把 null 或者 undefined 作为 this 的绑定对象传入call、apply或者bind,这些值在调用时会被忽略,this 指向全局对象。
例子:
var fn = function() {
console.log(this.name);
}
var name = "out";
var obj = {
name: "int",
getName: fn
}
var a = fn.bind(null);
a();
//输出:out
4、当用 new 运算符调用函数时,该函数总会返回一个对象,通常情况下,构造函数里的 this 就指向返回的这个对象。
例子:
var Fn = function(name, age) {
this.name = name;
this.age = age;
}
var obj = new Fn("Tom", 20);
console.log(obj.name);
//输出:Tom
箭头函数中的 this 绑定
上面介绍的四条规则已经可以包含所有正常的函数,但不包括 ES6 的箭头函数。由于 this 不再作为特殊的对象存在于箭头函数内部,因此,箭头函数会继承外层( 函数或者全局 )的 this 绑定。
例子1:
//继承外层函数的 this 绑定
<button name="btn">OK</button>
var name = "window";
var btn = document.querySelector("button");
btn.addEventListener("click", function() {
//this === btn;
(() => {
console.log(this.name)
})();
});
//输出:btn
例子2:
//继承全局的 this 绑定
<button name="btn">OK</button>
var name = "window";
var btn = document.querySelector("button");
btn.addEventListener("click", () => {
//this === window;
(() => {
console.log(this.name)
})();
});
//输出:window
其实,在 ES6 之前我们就已经在使用一种几乎和箭头函数完全一样的模式。
例子:
用 ES5 重写上面的例子1
<button name="btn">OK</button>
var name = "window";
var btn = document.querySelector("button");
btn.addEventListener("click", function() {
var that = this;
(function() {
console.log(that.name)
})();
});
//输出:btn
*function 声明的函数,永远不可能直接访问外层函数中的 this 对象,因此用变量 that 保存了对外层 this 的引用。
箭头函数仍然可以调用 call() 、 apply() 和 bind() 方法,但 this 值不会受影响。
例子:
var fn = () => {
console.log(this.name);
}
var name = "out"
var obj = {
name: "int"
}
fn.call(obj);
//输出:out
箭头函数不能通过 new 运算符调用,否则会导致程序抛出错误。
例子:
var Fn = (name, age) => {
this.name = name;
this.age = age;
}
var obj = new Fn("Tom", 20);
console.log(obj.name);
//输出:报错
运行时流程图
除了 this 绑定,arguments 取值也发生在编译阶段,由于不是重点,所以不再叙述,结合以上内容,JavaScript 的运行时流程图如下:
这张图会根据内容的增加不断进行补充。
如有错误,欢迎指正,本人不胜感激。