js中this的绑定规则及优先级

时间: 2018-12-06阅读: 335标签: this

一.   this绑定规则

函数调用位置决定了this的绑定对象,必须找到正确的调用位置判断需要应用下面四条规则中的哪一条。 


1.1 默认绑定

看下面代码:

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

var a = 1;

foo(); // 2

调用foo的时候,this应用了默认绑定,this指向了全局对象,但是在严格模式下,那么全局对象将无法进行默认绑定,因此this会绑定到undefined

function foo() {
    ‘use strict‘;

    console.log(this.a);
}

var a = 1;

foo();  // TypeRrror: this is undefined

严格模式下与 foo() 的调用位置无关:

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

(function(){
    "use strict";
    foo(); // 2
})();

 

1.2 隐式绑定

另一条需要考虑的规则是调用位置是否有上下文对象

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

但是无论是直接在 obj 中定义还是先定义再添加为引用属性, 这个函数严格来说都不属于 obj 对象,然而, 调用位置会使用 obj 上下文来引用函数, 因此你可以说函数被调用时 obj 对象“ 拥有” 或者“ 包含” 它

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

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

1.2.1 隐式丢失

一个最常见的 this 绑定问题就是被隐式绑定的函数会丢失绑定对象, 也就是说它会应用默认绑定, 从而把 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() 其实是一个不带任何修饰的函数调用, 因此应用了默认绑定。在js内置函数中如setTimeout也是如此:

function foo() {
    console.log(this.a);
}
var obj = {
    a: 2,
    foo: foo
};
var a = "oops, global"; // a 是全局对象的属性
setTimeout(obj.foo, 100); // "oops, global"

和下面伪代码类似:

function setTimeout(fn, delay) {
    // 等待 delay 毫秒
    fn(); // <-- 调用位置!
}

1.3.显示绑定

call(...),apply(...)可以指定this的绑定对象(前者接收多个参数如call(this, param1, param2, param3...),后者接受一个或两个参数apply(this, [...]))

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

通过 foo.call(..), 我们可以在调用 foo 时强制把它的 this 绑定到 obj 上。 如果你传入了一个原始值( 字符串类型、 布尔类型或者数字类型) 来当作 this 的绑定对 象, 这个原始值会被转换成它的对象形式( 也就是 new String(..)、 new Boolean(..) 或者 new Number(..))。 这通常被称为“ 装箱”。

1.3.1 硬绑定

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

我们创建了函数 bar(), 并在它的内部手动调用 了 foo.call(obj), 因此强制把 foo 的 this 绑定到了 obj。 无论之后如何调用函数 bar, 它 总会手动在 obj 上调用 foo。 这种绑定是一种显式的强制绑定, 因此我们称之为硬绑定。创建一个 i可以重复使用的辅助函数:

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

1.3.2 API调用的“上下文”

function foo(el) {
    console.log(el, this.id);
}
var obj = {
    id: "awesome"
};
// 调用 foo(..) 时把 this 绑定到 obj
[1, 2, 3].forEach(foo, obj);
// 1 awesome 2 awesome 3 awesome

这些函数实际上就是通过 call(..) 或者 apply(..) 实现了显式绑定, 这样你可以少些一些代码。

1.4new绑定

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

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

    2. 这个新对象会被执行 [[ 原型 ]] 连接

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

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

 

二. 优先级

毫无疑问, 默认绑定的优先级是四条规则中最低的,来看看隐式绑定和显示绑定

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

显式绑定优先级更高, new 绑定和隐式绑定的优先级谁高谁低:

function foo(something) {
    this.a = something;
}
var obj1 = {
    foo: foo
};
var obj2 = {};
obj1.foo(2);
console.log(obj1.a); // 2
obj1.foo.call(obj2, 3);
console.log(obj2.a); // 3
var bar = new obj1.foo(4);
console.log(obj1.a); // 2
console.log(bar.a); // 4

可以看到 new 绑定比隐式绑定优先级高。 但是 new 绑定和显式绑定谁的优先级更高呢?

function foo(something) {
    this.a = something;
}
var obj1 = {};
var bar = foo.bind( obj1 );
bar( 2 );
console.log( obj1.a ); // 2
var baz = new bar(3);
console.log( obj1.a ); // 2
console.log( baz.a ); // 3

bar 被硬绑定到 obj1 上, 但是 new bar(3) 并没有像我们预计的那样把 obj1.a 修改为 3。 相反,new 修改了硬绑定( 到 obj1 的) 调用 bar(..) 中的 this。 因为使用了 new 绑定, 我们得到了一个名字为 baz 的新对象, 并且 baz.a 的值是 3。之所以要在 new 中使用硬绑定函数, 主要目的是预先设置函数的一些参数, 这样在使用 new 进行初始化时就可以只传入其余的参数。 bind(..) 的功能之一就是可以把除了第一个 参数( 第一个参数用于绑定 this) 之外的其他参数都传给下层的函数( 这种技术称为“ 部 分应用”, 是“ 柯里化” 的一种)。 举例来说:

function foo(p1, p2) {
    this.val = p1 + p2;
}
//之所以使用 null 是因为在本例中我们并不关心硬绑定的 this 是什么
// 反正使用 new 时 this 会被修改
var bar = foo.bind(null, "p1");
var baz = new bar("p2");
baz.val; // p1p2

Nodejs中的this

以下内容都是关于在nodejs中的this而非javascript中的this,nodejs中的this和在浏览器中javascript中的this是不一样的。暂时我们先了解到这里,后面有机会我们会聊到module

JS匿名函数this调用变量报错

突然,看到附件选择的方法,人家是设置的this.fileUrl成功的。比对下,什么区别。这就是JS中匿名方法的this指向问题,function的声明方式,this指向的是本身,父级的变量都无法访问。

Js函数作用域、执行环境(this)、call、apply、bind 的用法

函数作用域:在 JavaScript 中,作用域为可访问变量,对象,函数的集合。JavaScript 函数作用域: 作用域在函数内修改。this对象是在运行时基于函数的执行环境绑定的。

vue项目中如何在外部js文件中直接调用vue实例——比如说this

一般我们都是在main.js中引入vue,然后在vue文件中直接使用this(this指向的是vue实例),但是在实际开发中,我们往往会引入外部的js文件使用this,这个this就会指向window,并不是我们期待的vue实例

this指向和apply,call,bind三者的区别

this指向,apply,call,bind的区别是一个经典的面试问题,同时在项目中会经常使用到的原生的js方法。同时也是ES5中的众多坑的一个。ES6中可能会极大的避免了this产生的错误

关于this

在参考了以上关于this的资料后,整理了一些个人认为比较有用的点,和一些自己的见解。写的有些仓促,不对的地方希望指正,共同进步。用call来解释this很形象啊,但是不是那么严谨,详情看补充1;

js手写实现apply,call,bind

apply和call的区别就是传的参数形式不一样。call是一个一个的传,apply可以将参数以数组的形式传进去。而bind是传入第二个和后面的参数,且绑定this,返回一个转化后的函数。

this指向问题的经典场景

以函数形式调用,this指向window;以方法形式调用,this指向调用方法的那个对象;构造函数调用,this指向实例的对象;使用window对象的方法使,指向window;多重场景改变this指向

改变this的指向

this是Javascript语言的一个关键字。它代表函数运行时,自动生成的一个内部对象.this永远指向函数的调用者。随着函数使用场合的不同,this的值会发生变化。但是有一个总的原则,那就是this指的是,调用函数的那个对象。

JavaScript函数中this的四种绑定策略

this的四种绑定策略:默认绑定、隐式绑定、显示绑定、new绑定。首先要明确的是一般情况下,this不是函数被定义时绑定,而是函数被调用时被绑定 ,那么函数中的this有四种绑定方式:

fly63.com版权所有,内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权,请与小编联系!情况属实本人将予以删除!

广告赞助文章投稿关于web前端网站点搜索站长推荐网站地图站长QQ:522607023

小程序专栏: 土味情话心理测试脑筋急转弯幽默笑话段子句子语录成语大全