你不知道的JavaScript--this

目录

 

困惑

调用点与调用栈

四种绑定规则

默认绑定(Default Binding)

 隐含绑定(Implicit Binding)

隐含丢失(Implicitly Lost)

明确绑定(Explicit Binding)

硬绑定(Hard Binding)

new 绑定(new Binding)

绑定优先级

判断方法:

绑定的特例

被忽略的 this

更安全的 this


 

困惑

对 this 的两大误解(不能说不正确,但肯定不准确)

  • this 指向自己
  • this 指向函数的作用域

 

调用点与调用栈

当一个函数被调用时,会建立一个称为执行环境的活动记录。这个记录包含函数是从何处被调用的,函数是如何被调用的,被传递了什么参数等信息。这个记录的属性之一,就是在函数执行期间将被使用的 this 引用。

这其中,函数代码中被调用的位置(注意,不是被声明的位置)就是调用点(call-site)。

是我们到达当前执行位置而被调用的所有方法的堆栈,就是调用栈(call-stack)

function baz() {
    // 调用栈是: `baz`
    // 调用点位于: global scope(全局)

    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`的调用点

 

四种绑定规则

this 不是编写时绑定,而是运行时绑定。它只与调用点和绑定方式有关,这里只需要记住4种绑定规则:

  • 默认绑定
  • 隐含绑定
  • 明确绑定
  • new绑定

 

默认绑定(Default Binding)

此规则适用于独立函数调用,这种 this 规则是在没有其他规则适用时的默认规则 

function foo() {
    // 指向全局
    console.log(this.a)
}

var a = 2

foo()    // 2

foo() 是被一个直白的,毫无修饰的函数引用调用的。此时使用默认绑定

 

 隐含绑定(Implicit Binding)

调用点是有一个 环境对象(context object),也称为拥有者(owning)或容器(containing)对象。

function foo() {
    // 指向容器
    console.log( this.a )
}

obj1 = {
    a: 2,
    foo: foo
}

obj1.foo()    // 2

obj1 对象在函数被调用的时间点上 “拥有” 或 “包含” 这个函数引用。

但要注意,只有对象属性引用链的最后一层是影响调用点的:

function foo() {
    // 指向最近的容器obj2
    console.log( this.a )
}

obj2 = {
    a: 2,
    foo: foo
}

obj1 = {
    a: 22,
    obj2: obj2
}

obj1.obj2.foo()    // 2

仔细想想,所谓的函数直接调用不就是window.fun()的简写?也是对象对象调用

隐含丢失(Implicitly Lost)

一个 隐含绑定 丢失了它的绑定,这通常意味着它会退回到 默认绑定(回调会丢失绑定):

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

obj1 = {
    a: 2,
    foo: foo
}

var bar = obj1.foo    // 一个函数引用!

var a = "oops,global!"

// 注意调用点!是一个直白无修饰的引用
bar()    // "oops,global!"

作用域章节可知 传参是一种隐含的赋值,所以传递回调函数时同上面情况一样:

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

function doFoo(fn) {    

    fn()    // 一个直白的引用
}

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

var a = "opps, global"

// 将obj.foo作为参数传递,实质是传递的 fn = obj.foo
doFoo( obj.foo )    // "opps, global"

 

明确绑定(Explicit Binding)

问:如何强制一个函数调用使用某个特定对象作为 this 绑定,而不在这个对象上放置一个函数引用属性呢(不用隐含绑定)?

答:通过JS的工具方法——call(…) 和 apply(…) 

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

var obj = {
    a:2
}

foo.call( obj )    // 2

明确绑定依旧会遇到绑定丢失的问题!

 

硬绑定(Hard Binding)

通过创建一个函数,并在它内部使用明确绑定来绑定this的方法叫硬绑定。

硬绑定用来解决绑定丢失的问题。

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

var obj = {
    a: "im obj"
}

var bar = function() {
    foo.call(obj)
}

var a = "im global"

bar()    // "im obj"

// 硬绑定不可以被覆盖!
bar.call(window)    // "im obj"

如上代码,我们创建了一个函数 bar(),在它的内部明确绑定 foo.call(obj),由此强制 this 绑定到 obj 并调用 foo。无论你过后怎样调用函数 bar,它总是手动使用 obj 调用 foo。这种绑定即明确又坚定,所以我们称之为 硬绑定。

硬绑定最典型的方法是,为所有传入的参数和传出的返回值创建一个通道:

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

var obj = {
    a: 2
}

// 硬绑定
var bar = function(arguments) {
    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,它的用法如下:

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

var obj = { a:2 };
var bar = foo.bind( obj );

var b = bar( 3 );    // 2 3 
console.log( b );    // 5

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

 

new 绑定(new Binding)

让我们明确一个观点:JS中,构造器仅仅是一个函数

构造器首字母大写。

JS的对象函数都可以在前面加上 new 来被调用,这使函数调用成为一个构造器调用(constructor call)。这是一个重要而微妙的区别:实际上不存在“构造器函数”这样的东西,而只有函数的构造器调用(通过new调用)

当在函数前面被加入 new 调用时,也就是构造器调用时,下面这些事情会自动完成:

  1. 一个全新的对象会凭空创建(就是被构建)
  2. 这个新构建的对象会被接入原形链([[Prototype]]-linked)
  3. 这个新构建的对象被设置为函数调用的 this 绑定
  4. 除非函数返回一个它自己的其他 对象,否则这个被 new 调用的函数将 自动   返回这个新构建的对象。

一个new绑定:

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

var bar = new foo(2)
console.log( bar.a )    // 2

 

绑定优先级

new绑定 > 明确绑定 > 隐含绑定 > 默认绑定

判断方法:

1、函数是通过 new 被调用的吗(new 绑定)?如果是,this 就是新构建的对象。

      var bar = new foo()

2、函数是通过 call 或 apply 被调用(明确绑定),甚至是隐藏在 bind 硬绑定 之中吗?如果是,this 就是那个被明确指定的对象。

      var bar = foo.call( obj2 )

3、函数是通过环境对象(也称为拥有者或容器对象)被调用的吗(隐含绑定)?如果是,this 就是那个环境对象。

     var bar = obj1.foo()

4、否则,使用默认的 this(默认绑定)。如果在 strict mode 下,就是 undefined,否则是 global 对象。

     var bar = foo()

 

绑定的特例

被忽略的 this

如果你传递 null 或 undefined 作为 call、apply 或 bind 的 this 绑定参数,那么这些值会被忽略掉,取而代之的是 默认绑定 规则将适用于这个调用。

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

var a =2

foo.call(null)    // 2

更安全的 this

当没有参数传递时,可以传递一个空对象 Object.create(null)

function foo(a,b) {
    console.log("a:" + a + ", b:" + b)
}

// 我们的 DMZ 空对象
var dmz = Object.create( null )

// 将数组散开作为参数
foo.apply( dmz,[2,3] )    // a:2,b:3

// 用 `bind(..)` 进行 currying
var bar = foo.bind( dmz,2 )
bar( 3 )    // a:2,b:3

 

展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 深蓝海洋 设计师: CSDN官方博客
应支付0元
点击重新获取
扫码支付

支付成功即可阅读