资讯专栏INFORMATION COLUMN

JavaScript中this终极理解(2)

liujs / 1900人阅读

摘要:找到函数的调用位置最重要的是要分析调用栈就是为了到达当前执行位置所调用的所有函数。显示绑定我们可以使用函数的和方法,通过这两个方法可以在某个对象上强制调用函数。

在上一篇我们了解过每个函数的this是在调用的时候绑定的,完全却决于函数的调用位置(也就是函数的调用方法)。

1. 调用位置

在理解this的绑定过程之前,首先要理解调用位置:调用位置就是函数在代码中被调用的位置,而不是声明的位置。

找到函数的调用位置最重要的是要分析调用栈(就是为了到达当前执行位置所调用的所有函数)。我们关心的调用位置就在当前正在执行的函数的前一个调用中。

function baz() {
    //当前调用栈是:baz
    //因此,当前调用位置是全局作用域
    console.log("baz");
    bar(); //<--bar的调用位置
}

function bar() {
    //当前调用栈是baz->bar
    //因此,当前调用位置在baz中
    console.log("bar")
    foo(); 
}

function foo() {
    //当前调用栈是baz->bar->foo
    //因此,当前调用位置在bar中
    console.log("foo")
}
baz() //<---baz得调用位置
2. 绑定规则

那么调用位置如何决定this得绑定对象呢。首先,我们要找到调用位置,然后按照下面四条规则进行应用。首先我们先了解下这几条规则:

(1)默认绑定

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

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

上面得代码中,在全局环境中声明了一个变量a,那么a就是全局对象得一个同名属性。当调用foo()时,this.a被解析成了全局变量a。因为函数调用得时候应用了this得默认绑定,因此this指向全局对象。
在代码中,foo()是直接使用不带任何修饰的函数引用进行调用,因此只能使用默认绑定

如果使用严格模式,那么全局对象将无法使用默认绑定,因此this会绑定到undefined。

(2)隐式绑定

另一条需要考虑的规则是调用位置是否有上下文对象,或则说是否被某个对象拥有或者包含。

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

首先需要注意的是foo()的声明方式,以及之后是如何被当作引用属性添加到obj中的。但是无论是直接在obj定义还是先定义再添加为引用属性,这个函数严格来说都不属于obj对象。

调用位置会使用obj上下文来引用函数,因此你可以说函数被调用时,obj对象"拥有"或者"包含"它。

当foo()调用时,它的落脚点确实指向obj对象。当函数引用有上下文对象时,隐式绑定规则会把函数调用中的this绑定到这个上下文对象。因为调用foo()时this被绑定到obj,因此this.a和obj.a时一样的。

对象属性引用链中只有最顶层或者最后一层会影响调用位置。

function foo() {
    console.log(this.a)
}
var obj2 = {
    a: 42,
    foo: foo
}
var obj1 = {
    a: 2,
    obj2: obj2
}
obj1.obj2.foo() //42

隐式丢失

this绑定一个常见的问题就是被隐式绑定的函数会丢失绑定对象,也就是说它会应用默认绑定,从而把this绑定到全局对象或者undefined上,这要取决于是否式严格模式。

function foo() {
    console.log(this.a)
}
var obj = {
    a:2,
    foo: foo
}
var bar = obj.foo; //函数别名
var a = "oops, globas"; //a式全局对象的属性
bar(); //"oops, globas"

虽然bar是obj.foo的引用,但实际上,它引用的是foo函数本身,因此此时的bar()其实是一个不带任何修饰的函数调用,因此应用默认绑定。

再看下面一组代码:

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

var obj = {
    a:2,
    foo:foo
}
var a = "oops,global"
doFoo(obj.foo); //"oops, global"

参数传递其实就是一种隐式赋值,因此我们传入函数时也会被隐式赋值,所以结果和之前一样。

(3)显示绑定

我们可以使用函数的call()apply()方法,通过这两个方法可以在某个对象上强制调用函数。
这两个方法的作用都是一样的,第一个参数是一个对象,它们会把这个对象绑定到this,接着在调用函数时指定这个this。因为可以直接指定this的绑定对象,因此叫做显示绑定

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

通过foo.call(..),我们可以在调用foo时强制把它的this绑定到obj上。

如果你传入了一个原始值(字符串类型,布尔类型等)来当作this的绑定对象,这个原始值会被转换成对象形式,也就是(new String(..)),这通常被称为"装箱"。

function foo() {
    console.log(this.a)
}
var obj = {
    a:2
}
var bar = function() {
    foo.call(obj)
}
bar();//2
setTimeout(bar, 10); //2
bar.call(window); //2

如上,我们不管怎么调用bar,它总会手动在obj上调用foo,这种绑定时一种显示的强制绑定,因此我们称为为硬绑定

硬绑定的典型应用场景就是创建一个包裹函数,传入所有的参数并返回接收到的所有值:

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

bind(...)会返回一个硬编码的新函数,它会把参数设置为this的上下文并调用原始函数。

(4) new绑定

在JavaScript中,构造函数只是一些使用new操作符时被调用的函数。他们并不会属于某个类,也不会实例化一个类。实际上他们甚至都不能说时一种特殊的函数类型。只是被new操作符调用的普通函数。

使用new来调用函数时会执行以下操作:

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

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

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

相关文章

  • JavaScriptthis终极理解(1)

    摘要:关键字是中一个复杂的机制,它被自动定义在所有的函数作用域中。指向它自身匿名函数无法指向自身第一个函数被称为具名函数,在它内部可以使用来引用自身。的绑定和函数声明的位置没有任何关系,取决于函数的调用方式。这是理解的前提。 this关键字是JavaScript中一个复杂的机制,它被自动定义在所有的函数作用域中。 1. 为什么要用this function identify() { ...

    YacaToy 评论0 收藏0
  • 笔试题之Event Loop终极

    摘要:下面开始分析开头的代码第一轮事件循环流程整体作为第一个宏任务进入主线程,遇到,输出遇到函数声明,声明暂时不用管遇到,其回调函数被分发到微任务中。我们记为遇到,其回调函数被分发到宏任务中。 先上一道常见的笔试题 console.log(1); async function async1() { console.log(2); await async2(); con...

    niceforbear 评论0 收藏0
  • 面试题之Event Loop终极

    摘要:下面开始分析开头的代码第一轮事件循环流程整体作为第一个宏任务进入主线程,遇到,输出遇到函数声明,声明暂时不用管遇到,其回调函数被分发到微任务中。我们记为遇到,其回调函数被分发到宏任务中。 先上一道常见的笔试题 console.log(1); async function async1() { console.log(2); await async2(); con...

    233jl 评论0 收藏0
  • jsthis的“终极三问”

    摘要:是什么本质是一个绑定,在函数被调用时建立。它的指向是完全由函数被调用的调用点来决定的。因为函数的调用点在全局作用域,所以指向全局变量这里就是函数的调用点存在的意义在函数体内部指代函数当前的运行环境。从而实现干净的设计和更容易的复用。 this是什么? this 本质是一个绑定, 在函数被调用时建立。它的指向是完全由函数被调用的调用点来决定的。 function baz() { ...

    silvertheo 评论0 收藏0
  • 终极蛇皮上帝视角之微信小程序之告别 setData

    摘要:而小程序官方的是在中调用方法来改变数据,从而改变界面。为了写测试让咱们来重构一把,利用学习过的函数式编程中的高阶函数把依赖注入。也就是说当中的某个数据更新的时候,我们并不知道它会影响哪个中的属性,特别的还有依赖于的情况。 众所周知 Vue 是借助 ES5 的 Object.defineProperty 方法设置 getter、setter 达到数据驱动界面,当然其中还有模板编译等等其他...

    wuyumin 评论0 收藏0

发表评论

0条评论

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