手记

JavaScript This 绑定

this 绑定

我们可以从以下两个方向理解 this 绑定的原则:

  1. 首先,从时间上来说,this 对象的赋值操作发生在编译阶段,也可以大致理解为发生在环境创建的时候,而非函数定义的时候。
  2. 其次,从空间上来说,内部函数在搜索 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 的运行时流程图如下:

这张图会根据内容的增加不断进行补充。


如有错误,欢迎指正,本人不胜感激。

2人推荐
随时随地看视频
慕课网APP