Javascript进阶2--this

系列文章:javascript

this 机制

误解

this 机制在 javascript 中是动态绑定,或称为运行期绑定的。这就致使 JS 中的 this 关键字会有多重含义,因此会给咱们形成一误解。学习 this 的第一步是明白this既不指向函数自身也不指向函数的词法做用域java

指向自身

人们容易把 this 理解成指向函数自身,看下面的代码bash

function foo(num) {
    console.log("foo: " + num
    this.count++;
}
// 这里为 foo 添加了一个属性 count,初始化为 0
foo.count = 0;
for(var i = 0; i < 10; i++) {
    if (i > 5) {
        foo(i);
    }
}
// foo: 6
// foo: 7
// foo: 8
// foo: 9
console.log(foo.count); // 0
// foo 确实被调用了4次,可是 foo.count 仍是 0,
// 是由于函数内部的 this 并非指向那个函数对象,
// 因此虽然属性名相同,可是根对象却不相同
复制代码

指向它的做用域

this指向函数的做用域,这个问题比较复杂,由于有时它是正确的,有时它的错误的。但能够明确的是:this在任何状况下,都不指向函数的词法做用域闭包

function foo() {
    var a = 2;
    this.bar();
}
function bar() {
    console.log(this.a);
}
foo();
复制代码

结果是 undefined ,由于这里经过 this.bar() 来引用 bar 函数,这里能调用成功是由于:app

  1. foo()被调用时,this === window,因此能够调用 bar 函数。
  2. bar执行时,this === window,使用对象属性访问规则,没有找到 a,因此返回undefined。

⚠️注意:这里有人会认为结果是 Reference Error 报错,其实和 RHS(Javascript进阶1--做用域和闭包 内有解释)混淆了。函数

this 解析

javascript中,this 是在运行时进行绑定的,它的上下文取决于函数调用时的各类条件。oop

绑定规则

先根据把绑定的优先级抛出结论,按照如下顺序进行判断:post

  1. 函数是否在 new 中调用(new 绑定)?若是是的话,this 绑定的是新建立的对象。学习

    var bar = new Foo()ui

  2. 函数是否经过 call、apply(显式绑定)或者硬绑定调用?若是是的话, this 绑定的是指定的对象。

    var bar = foo.call(obj2)

  3. 函数是否在某个上下文对象中调用(隐式绑定)?若是是的话,this 绑定的是那个上下文对象。

    var bar = obj1.foo()

  4. 若是都不是,使用默认绑定,严格模式下,绑定到 undefined,不然绑定到全局对象。

    var bar = foo()

new 绑定

在传统的面向类的语言中,构造函数是类的一些特殊方法,使用 new 初始化类时,会调用类中的构造函数。

首先,咱们须要澄清的一个误解是:在JS中,是没有构造函数的,在普通的函数调用前面加 new 关键字以后,就会把这个函数调用变成一个“构造函数调用”。实际上,new 会劫持全部普通函数并用构造函数的形式来调用它

那么在JS中,使用 new 来调用函数,var obj = new Foo(),会执行如下操做:

  1. 首先建立一个全新的空对象。

    var obj = {}

  2. 将空对象的原型 [[prototype]] 赋值为构造函数的原型.

    obj.__proto__ = Foo.prototype

  3. 更改 this 的指向。

    Foo.call(obj)

  4. 若 return 有值,而且值是对象,则直接返回此对象,不然,返回新建立的对象 obj。

如今看一段简单的代码检测一下学习成果

function foo1(a) {
    this.a = a;
}
function foo2(name) {
    this.name = name;
    return {
        w:1
    };
}
foo2.prototype.getName = function() {
    return this.name;
};
var bar = new foo1(2);
console.log(bar.a) // 2
var a = new foo2('hh');
a.getName();
// Uncaught TypeError: a.getName is not a function
// 这是由于,foo2 有返回值,而且是一个对象,因此a值是 {w: 1}
复制代码

显式绑定和硬绑定

若是想调用函数时,强制把函数的 this 绑定到 obj 上,可使用 call(...) 和 apply(...) 方法,它们的第一个参数一个对象,是给 this 准备的,这称之为显式绑定

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

⚠️注意:若是传入了一个原始值(布尔、数字、字符串等类型)来看成 this 的绑定对象,这个原始值会被转换成它的对象形式(也就是 new Boolean(...)、new Number(...)、new String(...))。

硬绑定就是显式绑定的一个变种,用于咱们将 this 被永久绑定到 obj 的 foo 函数,这样咱们就没必要每次调用 foo 都在尾巴上加上 call 那么麻烦。

function foo() {
    console.log(this.a)
}
var obj = {
    a: 2
};
var bindFunc = foo.bind(obj) 
bindFunc(); //2
复制代码

call/apply 与 bind 的区别是:call、apply将当即执行该函数,bind 不执行函数,只返回一个可供执行的函数。

隐式绑定

当一个函数被一个对象调用时,会把函数调用中的 this 绑定到这个上下文对象中。

fucntion foo() {
    console.log(this.a);
}
var obj = {
    a: 2,
    foo: foo
};
obj.foo()  //2
复制代码

⚠️注意:当咱们使用隐式绑定规则时,要注意下面两点:

  • 在参数传递、赋值时,隐式绑定的函数可能会丢失绑定对象,也就是会应用默认绑定规则。
    function foo() {
       console.log(this.a);
    }
    function doFoo(fn) {
       fn();
    }
    var obj = {
       a: 2,
       foo: foo
    };
    var a = 'oops, global';
    // obj.foo 引用 foo 函数自己,应用默认绑定
    doFoo(obj.foo); // oops, global
    
    var bar = obj.foo;
     // bar 引用 foo 函数自己,应用默认绑定
    bar(); // oops, global
    复制代码
  • 对象属性引用链中只有最后一层在调用位置中起做用。

默认绑定

当直接使用不带任何修饰的函数引用进行函数调用时,只能使用默认绑定,没法应用其余规则。在严格模式下,this 会绑定到 undefined,在非严格模式下, this会绑定到全局对象。

若是把 null 或者 undefined 做为 this 的绑定对象传入 call、apply或者 bind,会应用默认绑定规则。

箭头函数

箭头函数根据外层(函数或全局)做用域来决定 this。

function foo() {
    return (a) => {
        //  继承自 foo()
        console.log(this.a)
    };
}
var obj1 = {
    a: 2
};
var obj2 = {
    a: 3
};
var bar = foo.call(obj1);
bar.call(obj2) // 2
复制代码

⚠️注意:箭头函数的绑定没法被修改

好题练习

练习题1:

function Foo() {
    getName = function() {
        console.log(1);
    };
    return this;
}
// 静态方法赋值
Foo.getName = function() {
    console.log(2);
};
Foo.prototype.getName = function() {
    console.log(3);
};
var getName = function() {
    console.log(4);
};
function getName() {
    console.log(5);
}
Foo.getName(); //2

// 变量声明提高,赋值语句留在原地,因此结果是4
getName(); //4

// 在Foo函数中,重写了全局做用域下的 getName 函数
Foo().getName(); //1 
getName(); //1

// 等价于new (Foo.getName)(), 运算符优先级:成员访问 > new(不带括号) > 函数调用
new Foo.getName(); //2 

// 等价于 (new Foo()).getName(),运算符优先级:成员访问、new(带括号)的优先级同样,因此从左到右执行。
// 使用 new 实例 Foo 后生成的实例上,只有原型方法,没有静态方法
new Foo().getName();
复制代码

练习题2:

var title = 'world';
var a = {
    title: 'hello',
    alias: this.title,
    show() {
        console.log(this.title, this.alias)
    },
    ashow: () => {
        console.log(this.title, this.alias)
    }
}

a.show(); // hello world
var b = a.show; 
b(); // world undefined
a.ashow(); // world undefined
复制代码
相关文章
相关标签/搜索