动态绑定:JavaScript 中的 this 上下文
动态绑定:JavaScript 中的 this 上下文
[[工程师 JavaScript — 2]]
这个 JavaScript 中的关键字是上下文关键字,可以根据函数调用假设不同的不同值, 这个 旨在用作上下文对象。 this 关键字可以在函数内部或文件范围内使用。我们将涵盖确定这一点的所有规则,但让我们首先演示一些事情。
函数内部的这个想法:
(a) this 的值在调用常规函数之前是未知的。
( b ) 根据调用常规函数的方式,这将分配给上下文对象。
(c) this 的值不依赖于正则函数的定义
函数 abc(){
控制台.log(this.a); // (*)
} 常量 obj = { a: 100 };
常量 xyz = { a: [1,2,3,4] }; var a = "全球";
abc(); // (1)
abc.call(obj); // (2)
abc.call(xyz); // (3)
abc.call({}); // (4) // 代码结束
在上面的例子中,同一个函数 abc 用不同的 this 值执行。
首先请注意,这仅通过查看在第 (1)、(2)、(3)、(4) 行调用函数的调用站点来确定,
仅查看行 (*) 就无法预先知道任何事情
( d ) this 的值不取决于常规函数定义的位置
变量 a = 20;
常量 obj = {
一个:500,
fn:函数(){
控制台.log(this.a); // (*)
}
} obj.fn();
obj.fn.call({a: 700});
常量 myFun = obj.fn;
我的乐趣(); 在上面的代码中,很多人可能会觉得第 (*) 行的 this 总是 obj,因为函数定义似乎位于 obj 内部,这是一个错误的假设。 规则是这将取决于函数调用, 对象 obj 只包含一个引用函数对象的数据属性,
但这并不意味着该函数归对象所有/这将始终是 obj 。 事实上,myFun 也引用了同一个函数对象。所以所有权的想法是不存在的。
(e) 另一个错误的假设可能是 this 指向函数对象本身。
函数 abc(){
控制台.log(this.a); // (*)
控制台日志(这 === abc);
} 控制台.log(abc);
abc.call({a: 200});
abc();
abc.call(abc); 注意 this 的值在各种调用中是不同的,
在大多数调用中, this 的值不是 abc 指向的函数对象。
实际上,可以直接用函数名直接引用函数对象,这是一个动态的上下文,可以拥有不同的——不同的值。
( f ) 另一个错误假设是 this 指向函数调用时创建的作用域对象。
函数 abc(){
常数 a = 20;
控制台.log(this.a); // (*)
} abc.call({a: 200});
abc(); 如果这是范围对象,那么日志两次都应该是 20,但事实并非如此。
确定 this 的实际值需要了解一些规则并知道使用 this 的目的。
确定规则 这个 捆绑。
(a) 默认绑定:
当 this 关键字在文件范围内直接使用时,此绑定是默认绑定。
当一个变量直接指向一个常规函数对象,并且使用直接指向常规函数对象的变量直接调用该函数时,使用默认绑定。
当默认绑定规则适用时 this 的实际值取决于
1.严格的操作模式:这是未定义的
2.开发模式:这将是window/ globalThis
根据想法,默认绑定应该一直未定义,因为没有真正的上下文对象正在执行该函数。但是一个实现错误导致它是窗口对象,后来通过引入严格模式修复了这个错误。在实际情况下,通常不打算使用默认绑定调用函数。
// "使用严格"; // 取消注释以检查严格模式 默认绑定 var a = "窗口上定义的变量 a";
函数 abc(){
控制台.log(this.a); // (*)
} abc();
请注意,当行 (*) 正在执行时,作用域链看起来与上图相似,但这与外部词法环境没有真正的关系,尽管它们在上述情况下看起来是相同的。如果您不熟悉范围的概念,请参阅我的 [[Engineer JavaScript — 1]]。
让我们分析一下这个绑定的一个相当有趣的案例
功能外(){
console.log("外部",this.a); // ..... (#)
函数内部(){
console.log("内部", this.a);
}
内(); // 默认绑定 ....(*)
} external.call({a: "Hello World"}); // 显式绑定 在上面的代码中,看起来函数内部是在外部内部定义的,请记住,常规函数的这种绑定并不取决于函数的定义位置,而是函数在调用点的调用方式。 在上面的代码中,inner 是一个常规函数,它将在 outer 范围内创建,但这对调用 inner 时的 this 没有任何影响,
事实上,仔细看代码你会发现 inner 只是直接引用一个函数对象,而 inner 是直接调用的……所以默认绑定适用于 (*) 行, 事实上,(#) 处的 this 的值取决于函数 external 的调用方式。
( b ) 隐式绑定
当将常规函数 f 作为属性添加到对象 obj 上时,这将是用于调用函数 f 的对象,而不是包含该函数的对象。
隐式绑定的问题是对象可以相互连接,因此用于调用函数的对象可能与包含函数作为数据属性的对象不同。
函数 abc(){
控制台.log(this.a); // (*)
} 常量 secondChainObj = {
一个:1000,
fn: abc
} secondChainObj.fn(); // 这将是 abc 中的 secondChainObj 常量 firstChainObj = Object.create(secondChainObj);
firstChainObj.a = 500;
firstChainObj.fn(); // 这将是 abc 中的 firstChainObj const otherFirstObj = { a: "Hello World" };
Object.setPrototypeOf(otherFirstObj, secondChainObj);
otherFirstObj.fn(); // 这将是 abc 中的 otherFirstObj
所以将这个想法推广到一个更大的链上,即使在对象的 [[Prototype]] 链中找到一个函数,用于调用该函数的对象也将是 this
所以像这样的表达
first.fn();这将是第一个。
而在 first.second.third.fun();这将是 (first.second.third)
假设 fn, fun 是常规函数。
当我们讨论对象原型和 [[Prototype]] 链和原型继承时,我们将详细讨论这个想法。
(c) 隐式丢失的默认绑定
当一个普通的常规函数通过从一个对象属性中读取它被分配给一个变量 x 时。变量 x 现在直接引用函数对象,因此直接调用 x 会导致应用默认绑定
var a = "全局 a";
函数 abc(){
控制台.log(this.a); // (*)
} 常量 obj = {
一个:1000,
fn: abc
} 常量 cb = obj.fn; // cb 将在此赋值后直接引用函数对象
CB(); // 这将是默认绑定,检查默认绑定的定义
事实上,您现在可以像使用变量 abc 一样使用变量 cb。
一个相当重要的规则发生在作为回调传递的未绑定普通常规函数的情况下。下面的程序运行类似于以前的程序,因为 cb 直接指向函数对象。
var a = "全局 a";
函数 abc(){
控制台.log(this.a); // (*)
} 常量 obj = {
一个:1000,
fn: abc
} 函数 showImplictLost(cb) {
CB();
} showImplictLost(obj.fn); // cb 赋值为obj.fn,cb 直接指向函数对象
(d) 显式绑定
当使用 call 或 apply 调用常规函数时,第一个参数将是 this。
函数 f 上的 call 方法接受 this Arg 后跟 f 的参数(以逗号分隔),而函数 f 上的 apply 方法接受 thisArg 后跟 f 的参数在一个数组中。
函数 abc(x, y, z){
控制台.log(this.a); // (*)
console.log("变量 x, y, z 分别是", x, y, z);
console.log("参数总和 = ", x + y + z);
} abc.call({ a: "世界上最好的" }, 10, 20, 30 ); // 第一个参数是 this 常量 obj = {
一个:“对象一个”
}; abc.apply(obj, [12, 24, 36]); // 第一个 arg => obj
(e) 硬绑定和绑定函数
JavaScript 中的函数可以在创建函数的范围内形成闭包。这样一个封闭的作用域可以用来记住执行常规函数时应该是什么以及需要执行哪个函数。这样的策略将修复需要执行的函数以及需要传递给它的 this 上下文。硬绑定函数用于确保 this 是同一个对象的目的。
ES5 添加了 Function.prototype.bind 也允许绑定部分参数,第一个参数是 this 被永久绑定
函数 abc(x,y,z){
控制台.log(this);
控制台.log(this.a); // (*)
console.log("变量 x,y,z 分别是", x, y, z);
console.log("参数总和 = ", x + y + z);
} 常量 obj = {
一个:“对象一个”
}; // 绑定 this 为 obj , x 为 10
常量 boundFn = abc.bind(obj, 10); // ..... (1)
boundFn(20, 50); const anotherObj = { a: "印度" };
const reBindBoundFn = boundFn.bind(anotherObj); // .... (2)
reBindBoundFn(20, 30); 让我们了解 boundFn 是一个函数,它可以访问它记住一些变量的范围, 请注意,bind 的实现是这样的,当调用 bind 的返回值 boundFn 时,它使用闭包。 这样的 boundFn 可以很容易地使用,并保证 abc 将被调用 this 总是绑定对象 obj。 (2) 会发生什么?
我们试图看看boundFn建立的硬绑定是否被改变,答案是否定的。函数 abc 将再次使用 obj 调用 this。 为什么?
让我们详细了解一下 函数 f(){
console.log("Hello World"); } 注意这里的函数 f 不依赖于 this 因为它的定义不使用 this,
所以无论何时调用函数 f 时输出都是相同的, bind返回的函数的定义不依赖于this,所以尝试多次硬绑定不会有影响。 总之
函数 xyz(){
控制台.log(this.a);
} 常量 b1 = xyz.bind({ a: 500 });
常量 b2 = b1.bind({ a: 1000 });
常量 b3 = b2.bind({ a: 3000 }); b3(); 程序员用 this 作为默认绑定调用 b3
b3 以 { a: 3000 } 调用 b2,
b2 用 { a: 1000 } 调用 b1,
b1 以此为 { a: 500 } 调用 xyz
(f) 新绑定
当使用 new 运算符调用常规函数时,JavaScript 会执行 4 步机制。 new 运算符用于构造函数。我们将在另一篇文章中详细讨论 new 运算符、构造函数、继承链。
在 new Binding 的情况下,这将是函数开始执行时由 JavaScript 创建的空对象。新对象的 [[Prototype]] 将链接到函数的小写“prototype”属性
功能动物(名称,寿命){
this.name = 名称;
this.lifeSpan = lifeSpan;
} const dog = new Animal("dog", 20);
const human = new Animal("human", 60);
控制台日志(狗);
控制台.log(人类);
console.log(Object.getPrototypeOf(dog) === Animal.prototype);
( g ) this 在箭头函数中
到目前为止,我一直小心使用常规函数而不是箭头函数。原因是箭头函数与常规函数的想法不同。
this 内部箭头函数与创建时创建函数的范围内的函数相同。要实现的第二个重要的事情是箭头函数也使用范围闭包的想法来以某种方式创建硬绑定函数。箭头函数没有小写的“原型”属性。 new 运算符也不能与箭头函数一起使用。
因此,与 this 相关的箭头函数的唯一用例是,当您想使用与周围词法上下文中存在的相同 this 并通过硬绑定继续将其用作 this 时。由于箭头函数也创建了硬绑定函数,它们也可以用作回调函数。
函数whatIsThis() {
控制台.log(this.a); // 无论这将在这里
常量箭头Fn = () => {
console.log("绑定箭头 Fn", this.a); // 将通过闭包在此处可用
}
返回箭头Fn;
}
const boundThis = whatIsThis.call({a: 200});
绑定这个(); const boundAnotherThis = whatIsThis.call({a: 500});
boundAnotherThis();
此绑定的力量:
提到了此绑定的优先级(如果适用于规则),较高的优先级位于顶部,较低的优先级位于底部
箭头函数
新绑定
硬装订
显式绑定
隐式绑定
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· DeepSeek 开源周回顾「GitHub 热点速览」