资讯专栏INFORMATION COLUMN

JavaScript中的this

Bowman_han / 2542人阅读

摘要:通常寻找调用位置,最重要的是分析调用栈就是为了达到当前执行位置所调用的所有函数,而调用位置就是在当前正在执行的函数的前一个调用中。绑定在中,构造函数只是一些使用操作符时被调用的函数,它们并不属于哪一类,也不会实例化一个类。

this是什么

每个函数的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 的调用位置
绑定规则 默认绑定

最常用的函数调用类型:独立函数调用。可以把这条规则看做是无法应用其他规则时的默认规则
举例:

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

var a = 2

foo()   //2

在本例中。函数调用时应用了this的默认绑定,因此this指向全局对象。因为在代码中,foo()是直接使用不带任何修饰的函数引用进行调用的,因此只能默认绑定。

隐式绑定

调用位置是否有上下文对象,或者说是否被某个对象拥有或者包含。
举例:

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

var obj = {
    a: 2,
    foo: foo
}
obj.foo()   //2

调用位置使用obj上下文来引用函数,隐式绑定规则会把函数调用中的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"
显式绑定

使用函数的call()和apply()方法来直接指定this的绑定对象。
当传入了一个基本类型值来当做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;
}
// 简单的辅助绑定函数
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方法。

new绑定

在Javascript中,构造函数只是一些使用new操作符时被调用的函数,它们并不属于哪一类,也不会实例化一个类。实际上,它们甚至都不能说是一种特殊的函数类型,它们只是被new操作符调用的普通函数而已。
使用new来调用函数,或者说发生构造函数调用时,会自动执行下面的操作。

创建(或者说构造)一个全新的对象。

这个新对象会被执行[[prototype]]连接。

这个新对象会绑定到函数调用的this。

如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象。

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

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

判断this

函数是否在new中调用(new绑定)?如果是的话this绑定的是新创建的对象。

函数是否通过call、apply(显式绑定)或者硬绑定调用?this绑定的是指定的对象。

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

如果都不是的话,使用默认绑定。如果在严格模式下,就绑定到undefined,否则绑定到全局对象。

绑定例外

被忽略的this

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

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

var  a = 2

foo.call(undefined) //2

间接引用

你有可能有意无意的创建了一个函数的“间接引用”,在这种情况下,调用这个函数会应用默认绑定规则。
间接引用最容易在赋值时发生:

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

var a = 2;
var o = {a: 3, foo: foo}
var p = {a: 4}
o.foo()     // 3
(p.foo = o.foo)()       //2

因为赋值表达式p.foo = o.foo的返回值时目标函数的引用,因此调用位置是foo()而不是p.foo()。

软绑定

硬绑定会把this强制绑定到指定的对象(除了使用new时),防止函数调用应用默认绑定规则,但是硬绑定大大降低了函数的灵活性,使用硬绑定之后就无法使用隐式绑定或者显式绑定来修改this,所以我们使用软绑定,给默认绑定指定一个全局对象和undefined以外的值,就可以实现和硬绑定相同的效果,同时保留隐式绑定或者显式绑定this的能力。

if(!Function.prototype.softBind) {
    Funciton.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词法

ES6中介绍了一种无法使用上面那些规则的特殊函数类型:箭头函数。
箭头函数并不是function关键字定义的,而是使用=>定义的,箭头函数不适用this的四种标准规则,而是根据外层(函数或者全局)作用域来决定this的

function foo() {
    return (a) => {
        console.log(this.a)
    }
}

var obj1 = {
    a: 2
}

var obj2 = {
    a: 3
}

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

箭头函数最常用域回调函数中,例如事件处理器或者定时器

function foo() {
    setTimeout(() => {
        console.log(this.a)
    }, 100)
}

var obj = {a: 2}
foo.call(obj)   // 2

箭头函数像bind一样确保函数的this被绑定到指定对象上,此外,其重要性还体现在它用更常见的词法作用域取代了传统的this机制。

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

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

相关文章

  • JavaScript深入浅出

    摘要:理解的函数基础要搞好深入浅出原型使用原型模型,虽然这经常被当作缺点提及,但是只要善于运用,其实基于原型的继承模型比传统的类继承还要强大。中文指南基本操作指南二继续熟悉的几对方法,包括,,。商业转载请联系作者获得授权,非商业转载请注明出处。 怎样使用 this 因为本人属于伪前端,因此文中只看懂了 8 成左右,希望能够给大家带来帮助....(据说是阿里的前端妹子写的) this 的值到底...

    blair 评论0 收藏0
  • 学习React之前你需要知道的的JavaScript基础知识

    摘要:和类在开始时遇到类组件,只是需要有关类的基础。毕竟,中的条件呈现仅再次显示大多数是而不是特定的任何内容。 在我的研讨会期间,更多的材料是关于JavaScript而不是React。其中大部分归结为JavaScript ES6以及功能和语法,但也包括三元运算符,语言中的简写版本,此对象,JavaScript内置函数(map,reduce,filter)或更常识性的概念,如:可组合性,可重用...

    bitkylin 评论0 收藏0
  • JavaScript中的面向对象(object-oriented)编程

    摘要:对象在中,除了数字字符串布尔值这几个简单类型外,其他的都是对象。那么在函数对象中,这两个属性的有什么区别呢表示该函数对象的原型表示使用来执行该函数时这种函数一般成为构造函数,后面会讲解,新创建的对象的原型。这时的函数通常称为构造函数。。 本文原发于我的个人博客,经多次修改后发到sf上。本文仍在不断修改中,最新版请访问个人博客。 最近工作一直在用nodejs做开发,有了nodejs,...

    JerryZou 评论0 收藏0
  • JavaScript进阶之’this

    摘要:所以相同点是,在全局范围内,全局变量终究是属于老大的。只生效一次引入了。只生效一次在箭头函数中,与封闭词法环境的保持一致。我通常把这些原始函数叫做构造函数。在里面你可以嵌套函数,也就是你可以在函数里面定义函数。 showImg(https://img-blog.csdnimg.cn/20190522000008399.jpg?x-oss-process=image/watermark,...

    shenhualong 评论0 收藏0
  • 理解 JavaScript 中的 this 关键字

    摘要:原文许多人被中的关键字给困扰住了,我想混乱的根源来自人们理所当然地认为中的应该像中的或中的一样工作。尽管有点难理解,但它的原理并不神秘。在浏览器中,全局对象是对象。运算符创建一个新对象并且设置函数中的指向调用函数的新对象。 原文:Understanding the this keyword in JavaScript 许多人被JavaScript中的this关键字给困扰住了,我想混乱的...

    jayzou 评论0 收藏0

发表评论

0条评论

Bowman_han

|高级讲师

TA的文章

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