本文由图雀社区认证作者 婧婧 写作而成,点击阅读原文查看作者的掘金链接,感谢作者的优质输出,让我们的技术世界变得更加美好?
欢迎阅读阿里浩兄弟作品,头条刚的《你不知道的 Event Loop》:
本文会以详细讲解一道 字节面试题 的方式,循序渐进完全搞定 js 中 this 指向优先级的问题。 ⛹️♂️⛹️♂️ js 中的 this 指向问题应该是一个讨论了很久的话题了,关于这个话题的文章,在掘金也有很多。但是,可能之前看到的文章不怎么适合自己,每次看完都还是似懂非懂、没有多少头绪。前几天幸得我的老学长—— 猛哥[1] 的交流之后,好像对这个问题理解的更深了些,写篇文章总结一下。?
只要你仔细认真看完这篇文章,不管你是 js 新手、还是大佬,我保证你一定会有收获的!如果一丢丢收获都没有,你可以揍我!️?
Function
原型的 bind
(即手写下面代码块中的myBind) 方法,使得以下程序最后能输出 'success'
。function Animal(name, color) {
this.name = name;
this.color = color;
}
Animal.prototype.say = function () {
return `I'm a ${this.color} ${this.name}`;
};
const Cat = Animal.myBind(null, 'cat');
const cat = new Cat('white');
if (cat.say() === 'I\'m a white cat' &&
cat instanceof Cat && cat instanceof Animal) {
console.log('success');
}
先来看看解题需要了解的一些问题 慢慢来 不要慌张?
var name = "globalName";
function a() {
var name = "jingjing";
console.log(this.name)
}
a(); //globalName
根据刚刚上面那个原则:this 永远指向 最后调用它的那个对象 可以得到答案。我们看最后调用 a 的地方是在哪里?在最后一行代码a()
; 它前面没有调用的对象,那么就是默认的全局对象 window
,所以console.log(this.name)
就变成了console.log(window.name)
,结果输出的是 globalName
(?非严格模式下?)。
var name = "globalName";
var a = {
name: "jingjing",
jing: function () {
console.log(this.name); // jingjing
}
}
window.a.jing();
我又要重复上面那句话了?。this 永远指向 最后调用它的那个对象 可以得到答案。我们看最后调用 fn() 函数 的地方是在哪里?或者说函数 fn() 左边这个.
的左边的对象是哪个?显然是对象 a,所以console.log(this.name)
就变成了console.log(a.name)
,结果输出的是jingjing
。
var name = "globalName";
var a = {
name: "jingjing",
jing: function () {
console.log(this.name); // globalName
}
}
var hao = a.jing
hao();
? 这里我们虽然将 a 对象的 jing 方法赋值给变量 hao 了,但是注意!!!?当一个a. jing
返回的是一个函数,赋予给左边的变量时,此时这个变量接受的是一个普通函数,在全局上下文中调用,此时可以理解成window. hao()
然后执行,固内部的this指向全局对象。所以console.log(this.name)
就变成了console.log(window.name)
,结果输出的是 globalName
。再拿出这个原则:this 永远指向最后调用它的那个对象。???
在 JavaScript 中,this 指向的绑定规则有以下四种:
window
, 严格模式下,this指向 undefined
。)call()
、apply()
、bind()
调用,this 指向被绑定的对象。)new
调用,this
指向由 new
新构造出来的这个对象。)这种绑定方式就是使用 Function.prototype
中的三个方法 call()
, apply()
,和 bind()
了。这三个函数,都可以改变函数的 this 指向到指定的对象,不同之处在于:
call()
和 apply()
都是 立即执行函数 ,但是它们接受的参数的形式不同,具体如下:call(this, arg1, arg2, ...)
apply(this, [arg1, arg2, ...])
bind()
则是 返回一个新的包装函数,而不是立刻执行。bind()
会创建一个新函数。当这个新函数被调用时,bind()
的第一个参数将作为它运行时的 this
,之后的一序列参数将会在传递的实参前传入作为它的参数。bind(this, arg1, arg2, ...)
使用new来调用函数,或者说发生构造函数调用时,会自动执行下面的操作。
[[prototype]]
(即非es标准的_proto_
属性)绑定到构造函数的prototype
。在 JavaScript 中,new 操作符并不像其他面向对象的语言一样,而是一种模拟出来的机制。在 JavaScript 中,所有的函数都可以被 new 调用,这时候这个函数一般会被称为 “构造函数”,实际上并不存在所谓“构造函数”,更确切的理解应该是对于函数的 “构造器调用模式”。
new 绑定 > 显式绑定 > 隐式绑定 > 默认绑定
结论如上,下面给一些例子证明这个结论。
毫无疑问,默认绑定的优先级是四条规则中最低的,所以我们可以先不考虑它。
function jing() {
console.log(this.a);
}
var obj1 = {
a: 10,
foo: jing
};
var obj2 = {
a: 20,
foo: jing
};
obj1.foo(); //10
obj2.foo(); //20
obj1.foo.call(obj2); //20
obj2.foo.call(obj1); //10
代码
由这个运行结果可知,上面代码块倒数两行通过 call() 方法改变了 this 的指向。所以可以得到 显式绑定 > 隐式绑定 这个结论。
function jing() {
this.a = 'hao';
}
let obj = {
a: 'jing'
};
// 1、bind
const Bar = jing.bind(obj);
// 2、new
const bar = new Bar();
console.log(obj.a, '--', bar.a) //jing -- hao
代码
上面代码块倒数第三行通过 bind()
方法改变了 this
的指向obj
,由上面这个 bar.a
打印输出结果为 hao
可知,倒数第二行代码改变了this
指向 jing()
,所以可以得到 new绑定 > 显式绑定。
所以最后可以有此结论:new 绑定 > 显式绑定 > 隐式绑定 > 默认绑定
Function.prototype.myBind = function (thisObj, ...arg1) {
// 1、这里 ...arg1 是第一次传的参数
let fn = this;
// 这里用 fn 记下调用对象
function jingbind(...arg2) {
// 2、这里 ...arg2 是下一次传的参数
const args = arg1.concat(arg2);
// 谁调用 bind,最终拼好的参数就传给谁
return fn.apply(thisObj, args);
}
return jingbind;
// bind() 返回一个未执行的函数
}
代码
myBind()
,我们再写一个测试代码来试试效果:function sum(a, b, c) {
return a + b + c;
}
const sum10 = sum.myBind(null, 10);
let jing = sum10(20, 30);
console.log(jing) //60
??经过测试,成功输出正确结果。那我们现在可以试试我们刚刚实现的 myBind()
方法能不能解决文章顶部抛出来的面试题!想想都让人很兴奋!!?♂️?♂️
把上面那个方法和给出的题目放在一起,执行一下试试。
Function.prototype.myBind = function (thisObj, ...arg1) {
if (typeof this !== "function") {
throw new TypeError("not a function");
}
// 1、这里 ...arg1 是第一次传的参数
let fn = this;
// 这里用 fn 记下调用对象
function jingbind(...arg2) {
// 2、这里 ...arg2 是下一次传的参数
const args = arg1.concat(arg2);
// 谁调用 bind,最终拼好的参数就传给谁
return fn.apply(thisObj, args);
}
return jingbind;
// bind() 返回一个未执行的函数
}
function Animal(name, color) {
this.name = name;
this.color = color;
}
Animal.prototype.say = function () {
return `I'm a ${this.color} ${this.name}`;
};
const Cat = Animal.myBind(null, 'cat');
const cat = new Cat('white');
console.log(cat.say()) //这句代码是作者解题调试加上去的
if (cat.say() === 'I\'m a white cat' &&
cat instanceof Cat && cat instanceof Animal) {
console.log('success');
}
很遗憾、也很正常,报错了。错误信息截图如下:
cat.say() is not a function
,那它为什么不是一个方法呢?cat.say()
,一步一步往上追。cat
是哪里来的?是从 Cat
上 new
出来的实例,那这个 Cat
又是从哪里来的呢?是由 Animal.myBind
生成的,你调用了 myBind()
我给你返回的。那 myBind()
返回了什么呢?它返回的是 jingbind()
。所以最后我们能得到Cat === jingbind()
,即可以得到 cat === new jingbind()
。jingbind()
函数里面没有定义一个 say()
方法啊, 这个 say()
方法定义在 Animal.prototype
上面。Animal.prototype
上面有一个 say()
方法,但是经过我们写的 myBind()
方法处理后,把Animal.prototype
搞丢了。myBind()
代码如下:Function.prototype.myBind = function (thisObj, ...arg1) {
if (typeof this !== "function") {
throw new TypeError("not a function");
}
// 1、这里 ...arg1 是第一次传的参数
let fn = this;
// 这里用 fn 记下调用对象
function jingbind(...arg2) {
// 2、这里 ...arg2 是下一次传的参数
const args = arg1.concat(arg2);
// 谁调用 bind,最终拼好的参数就传给谁
return fn.apply(thisObj, args);
}
jingbind.prototype = fn.prototype; //只添加了这一行代码
return jingbind;
// bind() 返回一个未执行的函数
}
再执行一下试试
???
myBind()
函数里面没有做优先级的判断 (换句话说就是没有对不同的 this 绑定规则做出相应的 this 绑定)。我们对 myBind()
方法再做出一些改变如下:
Function.prototype.myBind = function (thisObj, ...arg1) {
if (typeof this !== "function") {
throw new TypeError("not a function");
}
// 1、这里 ...arg1 是第一次传的参数
let fn = this;
// 这里用 fn 记下调用对象
function jingbind(...arg2) {
// 2、这里 ...arg2 是下一次传的参数
const args = arg1.concat(arg2);
// 谁调用 bind,最终拼好的参数就传给谁
let isjing = this instanceof jingbind; //判断是否是 new 调用
return fn.apply(isjing ? this : thisObj, args);
}
jingbind.prototype = fn.prototype;
return jingbind;
// bind() 返回一个未执行的函数
}
instanceof
的作用不必多说instanceof
判断是不是通过 new 调用的,如果是 new 调用的 我们就要把 this 绑定到实例上去。myBind()
函数的第一个参数——thisObj
,这样处理一下我们应该能拿到想要的结果吧??再来测试一下?
最后的最后,我们把这个方法搞出来了。???
作者搭建了一个博客网站,准备从基础开始,不断更新,搭建完备的知识体系,为以后的面试做准备。如果是刚开始入门的小伙伴,有需要可以来看看哦?♂️?♂️
婧婧的成长之路[3]
有任何问题欢迎加作者微信交流学习(大佬忽略?)
[1]
猛哥: https://juejin.im/user/58ad9da68fd9c50067049cab
[2]
猛哥: https://juejin.im/user/58ad9da68fd9c50067049cab
[3]
婧婧的成长之路: https://jinghao.xyz/