this全面解析

最近在复习JS基础,所以这里将《你不知道的JavaScript》关于this的相关知识整理分享,记录学习过程。

上篇文章讲了js中的作用域,这里首先要强调一点就是,this在任何时候都不指向函数的作用域,作用域"对象"无法通过JavaScript代码访问,他存在于JavaScript引擎内部,每个函数的this都是在被调用的时候绑定的,完全取决于函数的调用位置。

一、绑定规则

1. 默认绑定

首先要说的就是独立函数调用,这条规则可以看成是无法应用其他规则时的默认规则。

看下面代码:

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

可以看到this.a被解析成了全局变量a,这就是因为在这里应用了this的默认绑定,也就是说,在代码中foo()是直接使用不带任何修饰的函数引用调用的,因此值能使用默认绑定

这里需要注意的是如果使用严格模式,就不能将全局对象用于默认绑定,因此this绑定会绑定到undefined。

2.隐式绑定

思考下面代码:

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

这里需要注意无论是直接在obj中定义还是先定义再添加为引用属性,这个函数严格来说都不属于obj对象。

但是当foo()函数被调用时,它的前面确实加上了对obj的引用。**当函数引用有上下文对象时,隐式绑定规则会把函数中的this绑定到这个上下文对象上。**因为调用foo时this被绑定到obj,因此this.a和obj.a是一样的。

3.显式绑定

上一条说了隐式绑定,那么如果现在我们不想在函数内部包含函数引用,而想在某个对象上强制调用函数,那么该怎么做呢?

这里我们可以使用callapply方法来做到显示绑定,这两个方法的原理就是:他们的第一个参数是一个对象,是给this准备的,接着在调用函数时将其绑定到this因为这样可以直接指定this的绑定对象,因此我们称之为显示绑定.

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

硬绑定是显示绑定的一个变种,可以解决this丢失问题,思考下面代码:

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

bar(); // 2
bar.call(window); // 2
复制代码

从上面代码可以看出,硬绑定的bar不可能再次修改它的this值,在这里我们可以创建一个简单的重复使用的硬绑定辅助函数bind:

function foo (something) {
    console.log(this.a, something);
    return this.a + something;
}

// 简单的辅助绑定函数
function bind(fn, obj) {
    return function () {
        return fn.apply(obj, arguments);
    }
}
var obj = {
    a:2
}
var bar = bind(foo, obj);
var b = bar(3); // 2  3
console.log(b);  // 5
复制代码

ES5中内置了方法Function.prototype.bind来实现这种硬绑定模式:

function foo(somthing) {
    console.log(this.a, something);
    return this.a + something;
}
var obj = {
    a:2
}
var bar = foo.bind(obj);
var b = bar();  // 2,3
console.log(b); // 5
复制代码
二、软绑定

硬绑定这种方式,可以把this强制绑定到指定的对象(除了使用new,后面讲),防止了函数应用默认绑定的同时也大大降低了函数的灵活度。

如果可以给默认绑定指定一个全局对象或者undefined之外的值,就可以实现和硬绑定相同的效果,同时也保留了隐式绑定或显示绑定修改this的能力。

思考下面软绑定代码:

if(!Function.prototype.softBind) {
    Function.prototype.softBind = function(obj) {
        var fn = this;
        var curried = [].slice.call(arguments, 1);
        var bound = function () {
            return fn.apply(
            (!this || this === (window || global)) ? obj : this,
            curried.concat.apply(curried, arguments);
            );
        }
        bound.prototype = Object.create(fn.prototype);
        return bound;
    }
}
复制代码

原理其实就是,首先检查调用时的this,如果this绑定到全局对象或者undefined,那就把指定的默认对象obj绑定到this,否则不会修改this。请看下面代码:

function foo () {
    console.log("name:  "+this.name);
}
var obj = {name: "obj"},
    obj2 = {name: "obj2"},
    obj3 = {name: "obj3"};
var fooObj = foo.softBind(obj);
fooObj(); // name: obj
obj2.foo = foo.softBind();
obj2.foo(); //  name: obj2    <-----!
fooObj.call(obj3);  // name: obj3   <-----!
复制代码

可以看到,软绑定版本的foo()可以手动将this绑定到obj2或者obj3上,但如果应用默认绑定,则会将this绑定到obj。

4.new绑定

js中实际上并不存在所谓的"构造函数",只有对于函数的"构造调用", 使用new来调用函数,或者说发生构造函数调用时,会自动执行下面步骤:

  1. 创建一个全新的对象
  2. 这个新对象会被执行[[Prototype]]连接
  3. 这个新对象会绑定到函数调用的this
  4. 如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象

请看下面代码:

function foo(a) {
    this.a = a;
}

var bar = new foo(2);
console.log(bar.a);
复制代码

使用new来调用foo()时,我们会构造一个新的对象并把它绑定到foo调用中的this上。

二、优先级

如果在某个调用位置应用了多条规则,如何确定哪条规则生效?

obj.foo.call(obj2); // this指向obj2 显式绑定比隐式绑定优先级高。
new obj.foo(); // thsi指向new新创建的对象 new绑定比隐式绑定优先级高。
复制代码

显式绑定和new绑定无法直接比较(会报错),默认绑定是不应用其他规则之后的兜底绑定所以优先级最低,最后的结果是:

显式绑定 > 隐式绑定 > 默认绑定 new绑定 > 隐式绑定 > 默认绑定

三、this绑定规则总结

  1. 对于直接调用的函数来说,不管函数被放在哪里,this都是window
  2. 对于被别人调用的函数来说,被谁"点"出来的,this就是谁
  3. 在构造函数中,类中(函数体中)出现的this.xxx=xxx中的this是当前类的一个实例
  4. call、apply时,this是第一个参数。bind要优于apply和call
  5. 箭头函数没有自己的this,需要看其外层是否有函数,如果有,外层函数的this就是内部箭头函数的this,如果没有,则this是window

转载于:https://juejin.im/post/5d52e198f265da03e05b08c9

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值