资讯专栏INFORMATION COLUMN

全面解析JS中的this

calx / 3302人阅读

摘要:当我们不想再对象内部间接包含引用函数,而像在某个对象上强制调用函数。我们可以用中内置的和的方法来实现,这两个方法的第一个参数是一个对象,是给准备的,接着再调用函数时将其绑定到。

this是什么

在javascript中,每个执行上下文可以抽象成一组对象

this是与执行上下文相关的特殊对象,任何对象都可以用作this上下文的值,一个重要的注意事项就是this值是执行上下文的属性,但不是变量对象的属性。这样的话,与变量相反,this值不会参与标识符解析,即在访问代码时,他的值直接来自执行上下文,也没有任何作用域链查找,在进入上下文中,this只能确定一次。所以this的值是和其所处的上下文环境有关系。

this全面解析
1.调用位置

在历届this的绑定之前,首先要理解调用位置,调用位置就是函数在代码中内调用的位置(而不是声明的位置),例子:

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的调用位置
2. 绑定规则
2.1 默认绑定

首先要介绍的最常见的函数调用类型:独立函数调用。可以把这条规则看作是无法应用其他规则时的默认规则

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

因为foo()在全局执行上下文中调用,所以this指向全局变量
如果使用严格模式,则不能将全局对象用于默认绑定,因此this会绑定到undefined:

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

虽然this的绑定规则完全取决于调用位置,但是只有foo()运行在非严格模式下时。默认绑定才能绑定到全局对象;在严格模式下调用foo()则不影响默认绑定

function foo(){
    console.log(this.a);
}
var a = 2;
(function(){
    foo(); //2
})()
2.2 隐式绑定
function foo(){
    console.log(this.a);
}

var obj = {
    a: 42,
    foo: foo
};

obj.foo();

当函数引用有上下文对象时,隐式绑定规则会把函数调用中的this绑定到这个上下文对象,因为调用foo()this被绑定到obj,因此this.aobj.a是一样的
对象属性引用链中只有上一层或者说最后一层在调用位置起作用

function foo(){
    console.log(this.a);
}

var obj2 = {
    a: 42,
    foo: foo
}

var obj1 = {
    a: 2,
    obj2: obj2
}

obj1.obj2.foo(); //42
2.3 隐式丢失

被隐式绑定的函数会丢失绑定对象,也就是说他会应用默认绑定,从而把this绑定到全局对象或者undefined

function foo(){
    console.log(this.a);
}

var obj = {
    a:2,
    foo: foo
};

var bar = obj.foo; //函数别名
var a = "oops, global"; //a是全局对象的属性。
bar(); //"oops, global"

虽然bar引用了obj.foo这个引用,但实际上他引用的是foo函数的本身。也就是说bar()是一个在全局上下文中调用的函数,因此this指向了全局对象。
这种情形页出现在参数传递中。

function foo(){
    console.log(this.a);
}

function doFoo(fn){
    fn();
}

var obj = {
    a: 2,
    foo: foo
};

var a = "oops, global"; //a是全局对象的属性
doFoo(obj.foo);

参数传递其实就是一种隐式赋值,因此我们传入函数也会被隐式赋值。

2.4 显示绑定

在分析隐式绑定时,我们必须在一个对象内部包含一个指向函数的属性,并通过这个属性间接引用函数,从而把this间接(隐式)绑定到这个都对象上。
当我们不想再对象内部间接包含引用函数,而像在某个对象上强制调用函数。我们可以用javascript中内置的applycall的方法来实现,这两个方法的第一个参数是一个对象,是给this准备的,接着再调用函数时将其绑定到this。因为你可以直接指定this的绑定对象,因此我们称之为显式绑定。

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

通过foo.call(...)我们可以在调用foo时强制把他的this绑定到obj上。如果你传入一个原始值(字符串类型,布尔类型或者数字类型)来当作this的绑定对象,这个原始值会被转换成他的对象形式,也就是“装箱”
我们通过显示绑定的变种解决绑定丢失的问题

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

bar(); //2
setTimeout(bar, 100); //2
//硬绑定的bar不可能在修改他的this
bar.call(window); //2

硬绑定的典型应用场景就是创建一个包裹函数,负责接收参数并返回值

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

var obj = {
    a: 2
}

var bar = function(){
    return foo.apply(obj, arguments);
} 
var b = bar(3); //2. 3
console.log(b); //5

另一种方式则是创建一个可以重复使用的辅助函数

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

var obj = {
    a: 2
}

function bind(fn, obj){
    return function(){
        return fn.apply(obj, arguments);
    }
}

var bar = bind(foo, obj);
var b = bar(3); //2 3
console.log(b); //5
2.5 apply,call,bind

之前介绍了applycall可以改变this的指向,现在来讲讲他们的区别以及ES5新增的方法bind
applycall之间最主要的区别在于传入参形式的不同。他俩的第一个参数都是指定了函数体内的this指向。
而第二个参数apply传入为一个带下标的集合,这个集可以为数组,也可以为类数组。apply方法把这个集合中的元素作为参数传递给被调用的函数

var func = function(a,b,c){
    alert([a, b, c]);  //1 2 3
}
func.apply(null, [1, 2, 3])

call传入的参数数量不固定,跟apply相同的是,第一个参数也是函数体内的this指向,从第二个参数开始往后,每个参数依次传入函数。

var func = function(a, b, c){
    alert([a, b, c]); //1 2 3
}
func.call(null, 1, 2, 3);

当使用call或者apply的时候,如果我们传入的第一个参数为null,函数体内的this会指向默认的宿主对象,在浏览器是window
大多数的高级浏览器已经实现了bind方法用来指定函数内部的this的指向

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

var bar = foo。bind(obj);

bar(); //2

bind(..)会返回一个硬编码的新函数,他会把你指定的参数设置为this的上下文并调用原始函数
我们也可以用apply模仿一个bind

Function.prototype.bind = function(){
    var self = this;
    var context = Array.prototype.shift().call(arguments);
    var args = Array.prototype.slice().call(arguments);
    return function(){
        this.apply(context, Array.prototype.concat.call(args, Array.prototype.shift().call(arguments);))
    }
}
var obj = {
    name: "foo"
};

var func = function(a, b, c, d){
    console.log(this.name);
    console.log([a, b, c, d]) //
}.bind(obj, 1, 2);

func(3,4);
2.6 new绑定

在javascript中,构造函数只是一些使用new操作符时调用的函数,它们并不会属于某个类,也不会是实例化一个类。
使用new来调用函数,或者说发生构函数调用时,会自动执行下面的操作
1.创建(或者说构造)一个去全新的对象。
2.这个新对象会被执行[[prototype]]连接
3.这个新对象会绑定到函数调用的this
4.如果函数没有其他返回对象,那么new表达式中的函数调用会自动返回这个新对象。

function foo(a){
    this.a = a;
}
var bar = new foo(2);
console.log(bar.a); //2
软绑定

前面我们说过硬绑定这种方式绑定之后无法修改this值,会降低函数灵活性。
如果可以给默认绑定指定一个全局对象和undefined以外的值,那就可以实现和硬绑定相同过的效果,同hi是保留隐式绑定或者显示绑定修改this的能力

 Function.prototype.softbind = function(){
     var self = this;
     var context = [].shift.call(arguments);
     var args = [].slice.call(arguments);
     var bound = function(){
     return self.apply((!this|| this === (window || global))?obj:this, [].concat.call(args, [].slice.call(arguments)));
     }
     bound.prototype =Object.create(self);
     return bound;
}
    function foo(){
        console.log(this.name);
    }
    var obj = {
        name: "obj"
    }
    var obj1 = {
        name: "obj1"
    }
    var fooobj = foo.softbind(obj, 1);
    fooobj();  //name: obj
    obj1.foo = foo.softbind(obj);
    obj1.foo();    //name: obj1
    setTimeout(obj1.foo, 10); //name: obj

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

this 语法

ES6中出现了不同与以上四种规则的特殊函数类型: 箭头函数。它是根据外层(外层或者全局)作用域来决定的

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!

foo()内部创建的箭头函数会捕获调用时foo()this,由于foo()this绑定到obj1,barthis也会绑定到obj1,箭头函数的绑定无法被更改。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/84996.html

相关文章

  • 【进阶3-1期】JavaScript深入之史上最全--5种this绑定全面解析

    摘要:在严格模式下调用函数则不影响默认绑定。回调函数丢失绑定是非常常见的。因为直接指定的绑定对象,称之为显示绑定。调用时强制把的绑定到上显示绑定无法解决丢失绑定问题。 (关注福利,关注本公众号回复[资料]领取优质前端视频,包括Vue、React、Node源码和实战、面试指导) 本周正式开始前端进阶的第三期,本周的主题是this全面解析,今天是第9天。 本计划一共28期,每期重点攻克一个面试重...

    xavier 评论0 收藏0
  • this全面解析(二)

    摘要:在传统的面向类的语言中,构造函数是类中的一些特殊方法,使用初始化类是会调用类中的构造函数。 在上一节中我们详细介绍了this的两种绑定方式,默认绑定和隐式绑定,在这一节我们继续介绍this的另外两种绑定方式显示绑定和new绑定。那么,我们要解决的问题当然就是上一节中我们提到的:this丢失! 显式绑定 在隐式绑定中,我们必须在一个对象的内部包含一个指向函数的属性,并通过这个属性间接引用...

    iflove 评论0 收藏0
  • 【进阶1-1期】理解JavaScript 中的执行上下文和执行栈

    摘要:首次运行代码时,会创建一个全局执行上下文并到当前的执行栈中。执行上下文的创建执行上下文分两个阶段创建创建阶段执行阶段创建阶段确定的值,也被称为。 (关注福利,关注本公众号回复[资料]领取优质前端视频,包括Vue、React、Node源码和实战、面试指导) 本周正式开始前端进阶的第一期,本周的主题是调用堆栈,,今天是第一天 本计划一共28期,每期重点攻克一个面试重难点,如果你还不了解本进...

    import. 评论0 收藏0
  • 全面解析this

    摘要:在严格模式下,对象的函数中的指向调用函数的对象实例显式绑定,,通过可以把的绑定到上。间接引用最容易在赋值时发生返回目标函数的引用词法之前介绍的种绑定规则可以包含所有正常的函数,但是中介绍了一种无法使用这些规则的特殊函数类型箭头函数。 this到底指向什么? this关键词是javaScript中最复杂的机制之一,一般有两个误区:1.this指向函数自身;2.this指向函数的作用域; ...

    Y3G 评论0 收藏0
  • this全面解析(一)

    摘要:调用栈就是为了到达当前执行位置所调用的所有函数。由于无法控制回调函数的执行方式,因此就没有办法控制调用位置得到期望的绑定,下一节我们会介绍如何通过固定来修复这个问题。 在《你不知道的this》中我们排除了对于this的错误理解,并且明白了每个函数的this是在调用时绑定的,完全取决于函数的调用位置。在本节中我们主要介绍一下几个主要内容: 什么是调用位置 绑定规则 this词法 调用...

    darry 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<