你小子,又在偷偷学this指向
序
深入了解V8执行流程,执行上下文和范围!如中所述,每当JavaScript执行一段可执行代码时,它都会创建一个相对于。每个执行上下文包含三个重要的属性:
词汇环境成分;
可变环境组件;
初始化此的值;
如果你对作用域和执行上下文不太了解,可以看一下上面的文章,里面讲述了V8的编译过程,以及作用域和执行上下文等一些比较难懂的概念。相信你看完会收获很多!
这是什么
与其他语言相比,JavaScript中函数的这个关键字的表达方式略有不同。另外,严格模式和非严格模式也有一些区别。在全局上下文中,它指向顶层对象(浏览器中的窗口),无论是否处于严格模式。
在大多数情况下,调用函数的方式决定了这个(运行时绑定)的值。这不能在执行过程中赋值,并且每次调用函数时其值可能不同。
这是在运行时绑定的,而不是在编写时绑定的,它的执行上下文取决于调用函数时的各种条件。
this的绑定与函数声明的位置无关,只取决于如何调用函数。
约束规则
默认绑定
首先要介绍的是最常用的一类函数调用:独立函数调用。想想下面的代码:
函数f00() {
console . log(this . a);
}
var a = 2;
foo();// 2
复制代码
在开头提到的文章中,说的是在全局范围内用var关键字声明的变量和在全局范围内声明的函数会被挂载到全局对象(window)上。
当我们调用foo()时,我们都知道全局声明的函数的作用域是浏览器中的顶级globalObject,也就是window。
通过观察我们可以看到,在代码中,foo()是用函数引用直接调用的,没有任何修饰,所以只能使用默认绑定。所以函数中的这个是window,也就是window.a,所以自然输出2。
如果使用严格模式,全局对象将不会用于默认绑定,因为这将被绑定到undefined
函数f00() {
“使用严格”;
console . log(this . a);
}
var a = 2;
f00();//无法读取未定义的属性(读取“a”)
//因为严格默认情况下,这是默认绑定为undefined的,所以this.a等于undivided.a
//因为undefined下没有A的属性,所以会报错类型。
复制代码
值得注意的是,如果foo()运行在非严格模式下,默认绑定可以绑定到全局对象,但严格模式下的foo()不影响默认绑定。
函数f00() {
console . log(this . a);
}
var a = 2;
(函数(){
“使用严格”;
f00();// 2
})();
复制代码
隐式结合
隐式绑定的规则是在调用位置是否有一个上下文对象,或者它是否被一个对象拥有或包含。但是,也不一定能这么说。首先,考虑下面的代码:
函数foo() {
console . log(this . a);
}
var obj = {
甲:111,
foo,
};
obj . foo();// 111
复制代码
首先要注意的是foo()是如何声明的,然后如何作为引用属性添加到abj对象中。但无论是直接在obj中定义,还是先定义后作为引用属性添加,这个函数严格来说都不是obj对象。
但是,调用位置会使用obj上下文来引用函数,所以在调用函数时,可以说obj对象“拥有”或“包含”了函数引用。
当一个函数引用一个上下文对象时,隐式绑定规则将在对这个上下文对象的函数调用中绑定它。所以this.a和obj.a是一样的。
例如,只有对象引用链的上层或最后一层在调用位置起作用
函数foo() {
console . log(this . a);
}
var obj2 = {
甲:111,
foo,
};
var obj1 = {
答:777,
obj2,
};
obj 1 . obj 2 . foo();// 111
//对象obj2是最后一层
// obj1.obj2仅用于属性查找,尚未调用。
复制代码
脱离原始上下文的函数
最常见的绑定问题之一是隐式绑定的函数将丢失绑定对象,这意味着它将应用默认的绑定默认值。
函数foo() {
console . log(this . a);
}
var obj = {
答:2,
foo,
};
var bar = obj.foo//函数别名
Var = "我是窗下";
bar();//我是一个窗下的人
复制代码
bar虽然是对obj.foo的引用,但实际上是引用foo函数本身,所以此时的bar()实际上是一个普通的函数调用,应用了默认绑定。
这实际上重新定义了一个bar函数,它和对象的结构一样,是重新赋值的。请参考代码:
函数foo() {
console . log(this . a);
}
var obj = {
答:2,
foo,
};
var { foo } = obj//这相当于重新定义一个函数或者它是一个函数别名。
Var = "我是窗下";
foo();//我是一个窗下的人
var object = {
时刻:777,
年龄:18,
};
console.log(对象);//{时刻:777,年龄:18}
var { moment } = object
Moment = "牛逼";
console.log(时刻);//牛逼
console.log(对象);//{时刻:777,年龄:18}
复制代码
上面的代码,解构后的变量moment,实际上是在全局范围内创建了一个变量moment,赋给777。后面对变量的直接修改并不会修改object对象中的属性矩。
作为参数的功能
函数foo() {
console . log(this . a);
}
功能栏(fn) {
// fn其实指的是foo。
fn();
}
var obj = {
答:777,
foo,
};
Var = "牛逼,那也行";
bar(obj . foo);//牛逼,这个就行。
复制代码
传递参数实际上是一种隐式赋值,所以我们在传入函数的时候也会被隐式赋值。上面的代码实际上是下面代码的变体:
函数foo() {
console . log(this . a);
}
功能栏(){
const fn = obj.foo
fn();
}
var obj = {
答:777,
foo,
};
Var = "牛逼,那也行";
bar();//牛逼,这个就行。
复制代码
显示绑定
在JavaScript中,您可以使用调用(...)并申请(...)方法,无论是宿主环境提供的一些函数,还是自己创建的函数。
它们的第一个参数是一个对象,是为此准备的,然后在调用函数时绑定到这个。因为可以直接指定这个的绑定对象,我们称之为显示绑定,
这里就不说apply和call的语法规则了。如有需要,可以咨询mdn官网。
硬装订
硬绑定可以将此强制绑定到指定的对象(new除外)。既然有硬绑定,自然就有软绑定,后面会讲到。
函数foo() {
console . log(this . a);
}
var obj = {
答:2,
};
var bar = function () {
foo . call(obj);
};
bar();// 2
setTimeout(巴,1000);// 2
//硬绑定栏不能再修改他的这个
bar.call(窗口);// 2
复制代码
应用方法具有相同的结果,但是参数的方式不同。
bind方法将返回一个硬编码的新函数,该函数将您指定的参数设置为这个上下文调用的原始参数。
API调用的“上下文”
JavaScript语言和宿主环境提供了许多内置函数,它们都提供了一个可选的参数,这个参数通常会成为一个上下文。其功能与bind(...).确保您的回调函数使用指定的this。
函数回调(元素){
console.log(element,this . id);
}
var obj = {
Id:“真好”,
};
//当foo(...)叫做
[1, 2, 3].forEach(回调,obj);
// 1 '真好看' 2 '真好看' 3 '真好看'
//我的地图也一样。
[1, 2, 3].map(回调,obj);
// 1 '真好看' 2 '真好看' 3 '真好看'
复制代码
新绑定
在我们开始讨论绑定之前,我想你已经知道当使用new调用构造函数时会发生什么。我们再来回顾一下:
在内存中创建一个新对象;
这个新对象内部的[[prototype]]属性被赋值为构造函数的prototype属性(不知道这个也可以点这里);
这个在构造函数里面被赋给这个新对象(也就是这个指向新对象);
执行构造函数内部的代码(给新对象添加属性);
如果构造函数返回非空对象,则返回该对象;否则,返回新创建的新对象;
函数Foo(力矩){
this.moment = moment
}
var bar = new Foo(777);
console . log(bar . a);// 777
复制代码
当使用new调用Foo(...),我们将构造一个新的对象,并在Foo中将其绑定到这个(...)打电话。
让我们考虑一下代码输出是什么:
var mayDay = {
瞬间:“瞬间”,
};
函数Foo() {
this.moment = 777
返回五月天;
}
var bar = new Foo();
console . log(bar . moment);
复制代码
输出的最终结果是moment,也就是这个绑定了五月天对象,那么为什么会这样呢?
答案在new的最后一个程序里“如果构造函数返回非空对象,就返回对象;否则,返回到新创建的“新对象”规则。
换句话说,如果构造函数返回一个对象,该对象将作为整个表达式的值返回,而传递的构造函数的这个将被丢弃。
如果构造函数返回非对象类型,则返回值被忽略,并返回新创建的对象。
var mayDay = {
瞬间:“瞬间”,
};
函数Foo() {
this.moment = 777
返回111;//这里的返回值已经更改
}
var bar = new Foo();
console . log(bar . moment);// 777输出的是新对象的矩。
复制代码
类上下文
这个类中的表现和函数中的表现类似,因为类本质上也是函数,但是有一些区别和考虑。在类的构造函数中,这是一个常规对象。类中的所有非静态方法都将添加到此的原型中:
课程示例{
构造函数(){
const proto = object . getprototypeof(this);
console . log(object . getownpropertymanames(proto));
}
first() {}
second() {}
Static third() {} //这不是在this上,而是在类本身上
}
新示例();// ['构造函数','第一个','第二个']
复制代码
箭头函数调用
箭头表达式的语法比函数表达式更简洁,它没有自己的this、arguments、super或new.target箭头表达式更适合那些需要匿名函数的地方,它不能作为构造函数使用。因为arrow函数不具备这一点,所以不能自然使用new运算符。
var moment = " moment
var bar = {
时刻:777,
常规:函数(){
console . log(this . moment);
},
箭头:()=> {
console . log(this . moment);
},
nest: function () {
var回调= () => {
console . log(this . moment);
};
回调();
},
};
bar . general();// 777
bar . arrow();//时刻
bar . nest();// 777
复制代码
第一个常见的功能是隐式绑定,我们前面提到过。
第二个调用,因为arrow函数没有自己的this,所以会在arrow函数的上一级寻找普通函数的this,然后就变成默认绑定,这是一个全局调用。
第三个和第二个差不多,但是它找的上层是函数嵌套,是隐式绑定,自然在对象内部输出monent。
虽然arrow函数不能通过call、applu、bind来绑定这个,但是它可以在cache arrow函数的上层绑定这个普通的函数,例如:
var foo = {
时刻:777,
常规:函数(){
console . log(this . moment);
return () => {
console.log("箭头:",this . moment);
};
},
};
var obj = {
瞬间:“瞬间”,
};
foo.general()。call(obj);// 777 "箭头:777 "
foo . general . call(obj)();//'时刻' '箭头:' ' '时刻'
复制代码
注意,这在settimeout和自执行函数中指向窗口。
setTimeout(函数foo() {
console . log(this);//窗口
}, 0);
(函数(){
console . log(this);//窗口
})();
复制代码
因为settimeout方法是挂载在window对象上的,所以在执行settimeout的时候,这个在执行回调中指向调用settimeout的对象,所以是window。
优先
如果可以对某个呼叫位置应用多个规则会怎么样?为了解决这个问题,必须优先考虑这些规则。显然,默认绑定的优先级是四个规则中最低的。
函数foo() {
console . log(this . a);
}
var obj1 = {
甲:666,
foo,
};
var obj2 = {
答:777,
foo,
};
obj 1 . foo();// 666
obj 2 . foo();// 777
obj 1 . foo . call(obj 2);// 777
obj 2 . foo . call(obj 1);// 666
复制代码
从上面的代码可以看出,显式绑定的优先级比隐式绑定高,也就是说,首先要考虑显式绑定能不能存在。
函数foo(年龄){
this.age =年龄;
}
var obj1 = {
foo,
};
var obj 2 = { };
obj 1 . foo(2);
console . log(obj 1 . age);// 2
obj1.foo.call(obj2,3);
console . log(obj 2 . age);// 3
var bar = new obj 1 . foo(7);
console . log(obj 1 . age);// 2
console . log(bar . age);// 7
复制代码
可以看出,新绑定的优先级高于隐式绑定,但是新绑定和显示绑定谁的优先级更高呢?
因为new和call/apply不能一起用,所以不能用new foo.call直接测试(...),但是我们可以用硬绑定来测试它们的优先级。
函数foo(年龄){
this.age =年龄;
}
var obj 1 = { };
var bar = foo . bind(obj 1);
酒吧(2);
console . log(obj 1 . age);// 2
var baz =新酒吧(3);
console . log(obj 1 . age);// 2
console . log(baz . age);// 3
复制代码
出乎意料的是,bar被绑定到obj1,但是new bar(3)并没有像我们语句中那样把obj1.age改为3。相反,new修改了硬绑定(到obj1)以在bar(...).
这是因为调用new时,bind之后的函数会忽略bind的第一个参数。稍后,我们将使用bind方法的ployfill实现来解释为什么会发生这种情况。
综上所述,他们的优先顺序是:
新电话;
调用、应用、绑定调用;
隐式绑定(对象方法调用);
默认绑定(普通函数调用);
绑定的Ployfill实现
function . prototype . bind = function(指针){
如果(键入此!== "函数"){
抛出新类型错误(
" Function.prototype.bind -试图绑定的内容是不可调用的"
);
}
//将参数转换为数组
const args = array . prototype . slice . call(arguments,1);
const self = this
const new func = function(){ };
const fBound = function () {
返回self.apply(
//如果是新的运算符,重新绑定这个
NewFunc && pointer的这个实例?这个:指针,
args . concat(array . prototype . slice . call(参数))
);
};
new func . prototype = this . prototype;
fbound . prototype = new new func();
返回fBound
};
复制代码
其中,以下是与新修改本相关的代码:
NewFunc && pointer的这个实例?这个:指针;
//...和;
new func . prototype = this . prototype;
fbound . prototype = new new func();
复制代码
软装订
正如我们前面提到的,这种硬绑定方法可以强制将其绑定到指定的对象(除非使用new),并防止函数调用应用默认的绑定规则。
但问题是硬绑定会大大降低函数的灵活性。在使用硬绑定之后,你不能使用隐式绑定或显式绑定来修改这个的能力。具体来说,请参见实现:
function . prototype . softbind = function(object){
设fn = this
//捕获所有定制的参数
const curried =[]. slice . call(arguments,1);
const bound = function () {
返回(
fn.apply(!this || this ===(窗口||全局)?对象:这个),
curried.concat.apply(curried,参数)
);
};
bound . prototype = object . create(fn . prototype);
返回绑定;
};
函数foo() {
console . log(this . name);
}
const obj = {
名称:“obj”,
};
const obj2 = {
名称:“obj2”,
};
const obj3 = {
名称:“obj3”,
};
const foo obj = foo . softbind(obj);
foo obj();// obj
obj 2 . foo = foo . softbind(obj);
obj 2 . foo();// obj2
foo obj . call(obj 3);// obj3
setTimeout(obj2.foo,1000);// obj
复制代码
如您所见,foo()的软绑定版本可以手动将其绑定到不同的对象。
参考文章
不知道JavaScript的书都卷起来了
MDN
结局
一点点this指向涵盖了new、call、apply、bind、arrow函数等的用法。从而延伸到范围、闭包、原型链、继承、严格模式,这个实力不可小觑。
本文链接:https://www.kkkliao.cn/?id=242 转载需授权!
版权声明:本文由廖万里的博客发布,如需转载请注明出处。