this是在运行时进行绑定的,并不是在编写时绑定,他的上下文取决于函数调用时的各种条件。this的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式。
当一个函数被调用时,会创建一个活动记录(有时也称为执行上下文)。这个记录会包含函数在哪里被调用(调用栈)、函数的调用方式、传入的参数等信息。this就是这个记录的一个属性,会在函数执行过程中用到。
调用位置就是函数在代码中被调用的位置(而不是声明位置)。最重要的就是分析 调用栈(就是为了达到当前执行位置所调用的所有函数)。 我们关心的调用位置就在当前正在执行的函数的前一个调用中。
function baz() {
// 当前调用栈 baz, 因此当前调用位置为全局作用域(当前正在执行的函数的前一个调用)
console.log("baz");
bar()
}
function bar() {
// 当前调用栈 baz -> bar, 因此当前调用位置为baz(当前正在执行的函数的前一个调用)
console.log("bar");
foo();
}
function foo() {
// 当前调用栈 baz -> bar -> foo, 因此当前调用位置为bar(当前正在执行的函数的前一个调用)
console.log("foo");
}
baz() // <--从这里开始, baz
函数的执行过程中调用位置决定this的绑定对象。
步骤就是, 找到调用位置然后判断需要使用下面的四条规则中的哪一条。
最常用的函数调用类型: 独立函数调用。可以把这看成无法应用其他规则的默认规则。
function foo() {
console.log(this.a);
}
var a = 2;
foo() // 2;
/**
首先第一件事: 声明在全局作用域中的变量(比如 var a= 2 )就是全局对象的一个同名属性。
调用foo时,this.a被解析成了全局变量a。因为在本例中,函数调用时应用了this的默认绑定,因此this
执行全局对象。那么我们怎么知道这里应用了默认绑定呢?可以通过分析调用位置来看。在代码中,foo()是
直接使用不带任何修饰的函数引用进行调用的,因此只能使用默认绑定。如果使用严格模式(strict mode),
则不能将全局作对象应用于默认绑定,因此this会绑定到undefined, 如下:
*/
function foo() {
"use strict";
console.log(this.a);
}
var a = 2;
foo() // TypeError: this is undefined
/**
这里有一个微妙但是非常重要的细节,虽然this的绑定规则完全取决于调用位置,但是只有foo()
运行在非strict mode下时(个人理解就是声明的函数体不在严格模式下的意思),默认绑定才能
绑定到全局对象;函数体声明于非严格模式,但是严格模式下调用foo()则不影响默认绑定
*/
function foo() {
console.log(this.a);
}
var a = 2;
(function (){
"use strict";
foo() // 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绑定问题就是被 隐式绑定 的函数会丢失绑定对象,也就是说它会应用 默认绑定。如:
*/
function foo() {
console.log(this.a);
}
var obj = {
a: 2,
foo: foo,
}
var bar = obj.foo;
var a = "oops, global";
bar(); // "oops, global";
/**
虽然bar是obj.foo的一个引用,但是实际上,它引用的是foo函数本身,因此此时的bar()其实是一个不带
任何修饰的函数调用,因此应用了默认绑定。一种更微妙更常见的情况发生在传入回调函数时:
*/
function foo() {
console.log(this.a);
}
function dooFoo(fn) {
fn(); // fn其实引用的是foo
}
var obj = {
a: 2,
foo: foo,
}
var a = "oops, global";
doFoo()(obj.foo); // "oops, global"
/**
参数传递其实就是一种隐式赋值,因此我们传入函数时也会被隐式赋值,所以结果和上一个例子一样。还有:
*/
function foo() {
console.log(this.a);
}
var obj = {
a: 2,
foo: foo,
}
var a = "oops, global";
setTimeout(obj.foo, 100); // "oops, global"
// js的setTimeout实现和下面的伪代码类似:
function setTimeout(fn, delay) {
// wait delay
fn() // <--调用位置
}
在分析 隐式绑定 时,我们必须在一个对象内部包含一个指向函数的属性,并通过这个属性间接引用函数,从而把this间接(隐式)绑定到这个对象上。但是我们其实可以使用call(...)和apply(...)在调用函数时指定它的this,因为你可以直接指定thia实我绑定对象,因此我们称之为显示绑定。
function foo() {
console.log(this.a);
}
var obj = {
a:2,
}
foo.call(obj) // 2
/**
在调用时强制把它的this绑定在obj上。如果你传入了一个原始值(字符串类型,布尔类型或数字类型)来当作
this的绑定对象,这个原始值会把转换成它的对象形式(new String()等,其实就是装箱)有不少内置API提供上
下文,确保你的回调函数使用指定的this,例如
*/
function foo(el) {
console.log(el, this.id);
}
var obj = {
id: 'awesome',
}
[1, 2, 3].forEach(foo, obj) // 1 awesome 2 awesome 3 awesome
首先我们重新定义一下js中的“构造函数”。在js中,构造函数只是一些使用new操作符时被调用的函数。他们并不会属于某个类,也不会实例化一个类。实际上他们甚至都不能说是一种特殊的函数类型,他们只是被new操作符调用的普通函数而已。
js中,实际上并不存在所谓的“构造函数”,只有对于函数的“构造调用”。
使用new来调用函数,会自动执行:
function foo(a) {
this.a = a
}
var bar = new foo(2);
console.log(bar.a); // 2
/**
使用new来调用foo()时,我们会构造一个新对象并把它绑定到foo(...)调用中的this上。
*/
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
/**
可以看到,显示绑定 优先级高于 隐式绑定。
*/
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
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 obj1.foo时, this优先获取到的是new是新创建的新对象,不是隐式绑定的obj1
/**
可以看到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 这边说明在new obj1.foo时, this优先获取到的是new是新创建的新对象,不是显示硬绑定的obj1
总结:
按顺序判断
ES6中介绍了一种无法使用这些规则的特殊函数类型: 箭头函数。
箭头函数不是使用function关键字定义的,而是使用被称为“胖箭头”的操作符=>定义的。箭头函数不使用this的四种标准规则,而是根据外层(函数或者全局)作用域来决定
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,
bar(引用箭头函数)的this也会绑定到obj1。箭头函数的绑定无法被修改(new 也不行!)
*/
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。