archerU / notes

博客笔记 预计写七个系列:JavaScript深入系列、JavaScript专题系列、数据结构和算法系列、设计模式系列、React系列、Webpack 系列、前端开发日常优化方案

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

JavaScript深入系列之this

archerU opened this issue · comments

this是一个很特别的关键字,被自动定义在所有函数的作用域中。

this既不指向函数自身也不指向函数的词法作用域。

this是在函数被调用时发生的绑定,它指向什么完全取决于函数在哪里被调用。

this 绑定规则优先级:new > call(..) 或者 apply(..) > 上下文对象调用 > 默认绑定

调用位置

调用栈:为了到达当前执行位置所调用的所有函数。
调用位置在当前正在执行的函数的前一个调用中。

function baz() {
    // 当前调用栈是:baz
    // 因此,当前调用位置是全局作用域
    console.log("baz");
    bar(); // <-- bar的调用位置
}

function bar() {
    // 当前调用栈是 baz -> bar 
    // 因此,当前调用位置在baz中
    console.log("bar");
    foo(); // <-- foo的调用位置
}

function foo() {
    // 当前调用栈是 baz -> bar -> foo 
    // 因此,当前调用位置在bar中
    console.log("foo");
}

baz(); // <-- baz的调用位置

注意我们是如何(从调用栈中)分析出真正的调用位置的,因为它决定了 this 的绑定。

绑定规则

默认绑定

var a = 2;
function foo() {
    console.log(this.a);   
}
foo(); // 2

独立函数调用,既使用不带任何修饰的函数引用进行调用,无法应用其他规则。非 static mode 下,this 默认绑定全局对象。strict mode 下,this 默认绑定 undefined。

隐式绑定

function foo() {
    console.log(this.a);
}
var obj = {
    a: 2,
    foo: foo
};
obj.foo(); // 2

当函数引用有上下文对象时,隐私绑定规则会把函数调用中的 this 绑定到这个上下文对象。

显示绑定

call(..) apply(..) bing(..)

function foo() {
    console.log(this.a);
}
var obj = {
    a: 2
};
foo.call(obj) // 2

通过 foo.call(..),我们可以在调用foo时强制把它的 this 绑定到 obj 上。

如果传入了一个原始值(字符串类型、布尔类型、数字类型)来当作 this 的绑定对象,这个原始值会被转换成它的对象形式(也就是 new String(..)、new Boolean(..)、new Number(..) )。

从 this 绑定的角度来说,call(..) 和 apply(..) 是一样的,它们区别体现在其他的参数上。

new绑定

使用 new 来调用函数,会自动执行下面的操作

  1. 创建(构造)一个全新的对象。
  2. 这个新对象会被执行[[原型]]连接。
  3. 这个新对象会绑定到函数调用的this。
  4. 如果函数没有返回其他对象,那么 new 表达式中的函数调用会自动返回这个新对象。
function foo(a) {
    this.a = a;
}
var bar = new foo(2);
console.log( bar.a ); //2

使用 new 来调用 foo(..) 时,我们会构造一个新对象并把它绑定到foo(..)调用中的 this 上。new 是最后一种可以影响函数调用时 this 绑定行为的方法。

绑定例外

被忽略的this

如果你把null 或者 undefined 作为 this 的绑定对象传入 call, apply 或者 bind,这些值在调用时会被忽略,实际应用的是默认绑定规则。

function foo() {
    console.log(this.a);
}
var a = 2;
foo.call( null ); // 2

bing(..) 可以对参数进行柯里化(预先设置一些参数)。

function foo(a,b) { 
    console.log("a:" + a + ",b:" + b);
}

// 把数组“展开”成参数
foo.apply(null,[2,3]); // a:2, b:3

// 使用 bind(..) 进行柯里化
var bar = foo.bind(null, 2);
bar(3); // a:2, b:3

这两种方法都需要传入一个参数当作 this 的绑定对象。如果函数并不关心 this 的话,你仍然需要传入一个占位值,这时 null 可能是一个不错的选择。

总是使用 null 来忽略 this 绑定可能产生一些副作用。如果某个函数确实使用了 this (比如第三方库中的一个函数),那默认绑定规则会把 this 绑定到全局对象(在浏览器中这个对象是window),这将导致不可预计的后果(比如修改全局对象)。

更安全的this

function foo(a,b) {
    console.log("a:" + a + ",b:" + b);
}

// 我们的 DMZ 空对象
var o = Object.create( null );

// 把数组展开成参数
foo.apply(o , [2,3]); // a:2, b:3

// 使用 bind(..) 进行柯里化
var bar = foo.bind(o, 2);
bar(3); // a:2, b:3

JavaScript 中创建一个空对象最简单的方法都是 Object.create(null)Object.create(null) 和 {} 很像,但是并不会创建 Object.prototype 这个委托,所以它比 {} "更空"。

箭头函数

箭头函数不使用 this 的四种标准规则,而是根据外层(函数或者全局)作用域来决定 this。

function foo() {
    //返回一个箭头函数
    return (a) => {
        // this 继承自 foo()
        console.log(this.a);
    };
}

var obj1 = {
    a: 2
};
var obj2 = {
    a: 3
}

var bar = foo.call( obj1 );
bar.call( obj2 );  // 2,不是 3 !