Javascript系列之this绑定(下)

133 阅读1分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第2天,点击查看活动详情


关于this的误解

  1. 从英语语法角度来说很容易把this理解成指向函数自身,我们可以从下面代码发现this并没有指定为函数自身
function foo(num) {
    console.log("foo"+num);
    this.count++;
}
foo.count = 0;
for(let i = 0; i < 5; i++){
    foo(i)
}
console.log(foo.count) //输出0

那么this指向哪里了呢,实际上,count加在了全局对象上,成为了一个全局属性,并且它的值为NaN

在很多时候我们需要引用函数自身,比如说递归调用。在以前我们可以使用arguments.callee进行调用自身,但是现在已经被弃用了。这个时候我们可以使用显示绑定比如call强制把this绑定到函数上。

  1. 还有一种常见误解,this指向函数的作用域,这在某种情况下表现是正确的,但是其他情况是错误的。

需要注意的是:this在任何情况下都不指向函数的词法作用域。在javascript内部,作用域和对象类似,可见标识符都是它的属性。但是作用域对象无法通过JavaScript代码访问,作用域仅存在于JavaScript引擎内部。

function foo() {
    var a = 1;
    this.bar();
}
function bar() {
    console.log(this.a);
}
foo(); //ReferenceError: a is not defined

绑定例外

  1. 被忽略的this 如果将nullundefined作为this绑定的对象传入callapplybind,这些值在调用时会被忽略,实际采用默认绑定规则
function foo() {
    console.log(this.a);
}
var a = 1;
foo.apply(null); // 1

一般传入null的应用场景是使用apply(...)来展开一个数组,并当作参数传入一个函数。 类似的bind(...)可以对参数进行柯里化:

function foo(a, b) {
    console.log("a: "+ a + ", b: " + b);
}
foo.apply(null, [1, 2]); //a: 1, b: 2

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

这两种方法都需要传入一个参数当作this的绑定对象。当函数不会使用this的话这样没有问题。然而 ,当这个函数中确实使用了this就会可能产生一些副作用,默认绑定规则会将this绑定到全局对象,可能导致代码混乱。

解决方法: 这种情况下我们可以创建一个空对象作为参数传递。这样所有对this的操作都会被限制在这个空对象中,不会对全局对象产生影响。Object.create(null)可以创建一个空对象,它比{}更适合,应为它并不会创建Object.prototype这个委托。

  1. 间接引用
function foo() {
    console.log(this.a);
}
var a = 1;
var o = {a: 2, foo: foo};
var o2 = {a: 3};
o.foo(); // 2

(o2.foo = o.foo)(); // 1

这个可以根据函数的引用只是一个指向函数的地址来理解, 赋值表达式o2.foo = o.foo的返回值是目标函数的引用,所以调用位置是foo()而不是o.foo()或者o2.foo()

  1. 软绑定 由于硬绑定可以把this强制绑定到指定对象,但是之后不能使用隐式绑定或者显示绑定修改this。所以有了软绑定: 给默认绑定一个全局对象和undefined以外的值,那就可以实现和硬绑定相同的效果,同时保留隐式绑定或者显示绑定修改this的能力。

  2. 箭头函数 箭头函数会捕获调用时的this,并且箭头函数的绑定无法被修改。箭头函数可以像bind(...)一样确保this被绑定到指定对象。

function foo() {
    setTimeout(() => {
        console.log(this.a)
    }, 1000);
}
var obj = {a: 1};
foo.call(obj); // 1