理解this
1.1 为什么使用this
下面的代码中,当调用对象的方法时,希望将对象的名称一起打印出来
const obj = { name: 'marlboroKay', running(){ console.log(`${obj.name} can running`); }, studying(){ console.log(`${obj.name} can studying`); }, playing(){ console.log(`${obj.name} can playing`); }, } obj.running(); //marlboroKay can running obj.studying(); //marlboroKay can studying obj.playing(); //marlboroKay can playing
但是上面的代码存在一个弊端,如果将对象名称obj修改为 object,那么所有方法中的obj.name都需要替换成object.name。
在日常的业务代码中,通常会使用this来指向obj
const object = { name: 'marlboroKay', running(){ console.log(`${this.name} can running`); }, studying(){ console.log(`${this.name} can studying`); }, playing(){ console.log(`${this.name} can playing`); }, } object.running(); //marlboroKay can running object.studying(); //marlboroKay can studying object.playing(); //marlboroKay can playing
所以,在某些函数或者方法中,this可以更加清晰便捷的引用对象。使代码逻辑变得清晰且易于复用
1.2 this指向什么?
//定义函数foo let foo = function(){ console.log(this); } //1.直接调用 foo(); //Window //2.将foo放入对象中,再调用 let obj = { name: 'marlboroKay', foo, } obj.foo(); //{name: "marlboroKay", foo: ƒ} obj对象 //3.通过call调用 foo.call("this") //String {"this"}
通过上面的三种不同调用,可以发现:
i:函数调用时,JavaScript会默认给this绑定一个值
ii:this 的绑定和定义的位置(代码的位置)无关,和调用方式和调用位置有关
iii:this 是在运行时被绑定的
1.3 this绑定规则
1.3.1 默认绑定:函数调用时无任何调用前缀
普通函数调用
let foo = function(){ console.log(this); } foo(); //Window //通常默认绑定时,函数中的this指向全局对象(window) let foo = function(){ 'use strict' console.log(this); } foo(); //undefined //严格模式下,指向undefined
函数调用链(一个函数内部调用另一个函数)
function foo1(){ console.log(this); foo2(); } function foo2(){ console.log(this); foo3(); } function foo3(){ console.log(this); } foo1(); // Window // Window // Window //所有函数在被调用时,都没有调用前缀,所以仍然使默认绑定
函数作为参数,传入到另一个函数中
function foo(func){ func(); } function bar(){ console.log(this); } foo(bar); // Window
函数作为参数扩展
function foo(func){ func(); } const obj = { name: 'marlboroKay', bar(){ console.log(this); } } foo(obj.bar); //Window
当foo(obj.bar)执行时, 当前位置没有任何对象绑定,仍然是一个默认绑定。(可以看成跟3.函数作为参数,传入到另一个函数中)
1.3.2 隐式绑定:函数调用的所在位置中,是通过某个对象发起的函数调用
对象调用函数
let foo = function(){ console.log(this); } const obj = { name: 'marlboroKay', foo, } obj.foo(); //{name: "marlboroKay", foo: ƒ} obj对象
foo调用的位置为obj.foo(),当foo调用时,会隐式的绑定到obj对象上
就近调用
let foo = function(){ console.log(this); } const obj = { name: 'marlboroKay', foo, } const obj2 = { name: 'obj2', obj, } obj2.obj.foo() //{name: "marlboroKay", foo: ƒ} obj对象
隐式丢失
let foo = function(){ console.log(this); } const obj = { name: 'marlboroKay', foo, } let bar = obj.foo; bar() //Window
foo最终被调用的位置是bar,而bar在调用时,没有绑定任何对象,也就没有产生隐式绑定。(相当于一个默认绑定,所以this最终指向Window)
1.3.3 显示绑定
隐式绑定有个前提:函数调用的所在位置中,是通过某个对象发起的函数调用。但是如果不想通过对象引用函数的方式来进行函数调用,又同时想在这个对象上进行强制的函数调用,该如何处理呢?
JavaScript 中所有的函数都可以使用call和apply方法
call和apply的区别,简单描述:第一个参数时相同的,第二个参数,call为参数列表,apply为数组。第一个参数要求传入为对象,当调用call或apply时,会将this绑定到出入的对象上。
由于明确将this绑定到传入的对象上,所以这种绑定方式称为显示绑定。
call绑定this对象
let foo = function(){ console.log(this); } foo.call(window); //Window foo.call(666); //Number {666} foo.call({name: 'marlboroKay'}) //{name: "marlboroKay"}
bind绑定:一个函数总是显示绑定到一个对象上,可以采用bind()。bind()会返回一个新的函数,把this绑定到第一个传入的参数上
let foo = function(){ console.log(this); } const obj = { name: 'marlboroKay', } let bar = foo.bind(obj); bar(); // {name: "marlboroKay"}
内置函数
setTimeout(function(){ console.log(this) }, 1000) //Window //setTimeout会传入一个回调函数,该函数的this通常时指向Window [1, 2, 3].forEach(function(){ console.log(this) }) //Window //Window //Window //forEach()会传入一个回调函数,默认情况下,执行默认绑定 //forEach()第二个可选参数,可以设置this绑定对象 [1, 2, 3].forEach(function(){ console.log(this) }, 'marlboroKay') //String {"marlboroKay"} //String {"marlboroKay"} //String {"marlboroKay"}
1.4 new 绑定
JavaScript中,用new修饰的函数,为函数的构造调用,new关键字调用函数时,会执行如下操作:
i:创建一个全新的对象
ii:新对象的 \\_\\_proto\\_\\_ 指向原函数的 prototype属性。(继承原函数的原型)
iii:新对象会绑定到函数调用的this上。(this绑定在这一步完成)
iiii:如果原函数没有返回其他对象,则返回这个新对象
function Foo(name){ console.log(this); this.name = name; } let foo = new Foo('M'); console.log(foo); //Foo {} //Foo {name: "M"}
使用new调用函数后,函数会原函数名称命名并创建一个新的对象,并返回
注意:如果原函数返回一个对象类型,则无法返回新对象,将丢失this绑定的新对象
function Foo(name){ this.name = name; return {name: 'MarlboroKay'} } let foo = new Foo('M'); console.log(foo) //{name: "MarlboroKay"} console.log(foo.name) // MarlboroKay
1.5 规则优先级
默认规则的优先级是最低的,因为存在其他规则时,就会通过其他规则的方式来绑定this
显示绑定与隐式绑定谁的优先级更高呢?
function foo(){ console.log(this); } const obj = { name: 'obj', foo, } const obj2 = { name: 'obj2', foo, } obj.foo() //{name: "obj", foo: ƒ} obj2.foo() //{name: "obj2", foo: ƒ} obj.foo.call(obj2) //{name: "obj2", foo: ƒ}
说明显示绑定 > 隐式绑定
new绑定与隐式绑定,显示绑定的优先级谁更高呢?
new 与 隐式绑定:
function foo(){ console.log(this); } const obj = { name: 'obj', foo, } new obj.foo(); //foo {}
说明new > 隐式绑定
new 与 显示绑定(call,apply):
function foo(){ console.log(this); } const obj = { name: 'obj', } let bar = new foo.call(obj); //Uncaught TypeError: foo.call is not a constructor
new绑定和call、apply是不允许同时使用的,所以不存在谁的优先级更高
new 与 显示绑定(bind):
function foo(){ console.log(this); } const obj = { name: 'obj', } let bar = foo.bind(obj); new bar(); // foo {}
说明new > bind
优先级总结:new绑定 > 显示绑定 > 隐式绑定 > 默认绑定
2. this规则之外
2.1 忽略显示绑定:
如果在显示绑定中,传入一个null或者undefined,那么这个显示绑定会被忽略,使用默认规则:
function foo(){ console.log(this); } const obj = { name: 'obj', } foo.call(obj) // {name: "obj"} foo.call(null) //Window foo.call(undefined) //Window
2.2 函数的间接引用
//间接引用 let num = 1; let num2 = 2; let res = (num = num2) ;console.log(res) //2 //函数间接引用 function foo(){ console.log(this); } const obj = { name: 'obj', foo, } const obj2 = { name: 'obj2', } obj.foo(); //{name: "obj", foo: ƒ} (obj2.foo = obj.foo)(); // Window
(obj2.foo = obj.foo)的结果是 foo 函数,foo函数被直接调用,是默认绑定
2.3 ES6 箭头函数:
箭头函数不使用this的四种标准规则(也就是不绑定this),而是根据外层作用域来决定this
//隐式绑定丢失 var name = 'THIS'; const obj = { name: 'marlboroKay', getName(){ console.log(this.name); }, } let foo = obj.getName; foo(); //"THIS" //ES6 箭头函数 var name = 'THIS'; const obj = { name: 'marlboroKay', getName(){ return () => { console.log(this.name) } }, } let foo = obj.getName(); foo(); //marlboroKay
因为箭头函数并不绑定this对象,那么this引用就会从上层作用域中找到对应的this。
如果getName() 也是一个箭头函数,那么foo()会返回什么呢?
const obj = { name: 'marlboroKay', getName: () => { return () => { console.log(this); } }, } let foo = obj.getName(); foo(); //Window
依然是不断的从上层作用域找,那么找到了全局作用域,在浏览器的全局作用域内,this代表的就是window
3. this 面试题
3.1
var name = 'Window'; const obj = { name: 'MarlboroKay', getName(){ console.log(this.name); }, } function getName(){ let gName = obj.getName; gName(); obj.getName(); (obj.getName)(); (temp = obj.getName)() } getName();
----------------------答案分割线----------------------
//Window //MarlboroKay 隐式绑定 //MarlboroKay //Window
3.2
var name = 'Window'; const obj = { name: 'MarlboroKay', foo(){ console.log(this.name); }, foo2:() => console.log(this.name), foo3(){ return function(){ console.log(this.name); } }, foo4(){ return () => { console.log(this.name) } }, } const obj2 = {name: 'obj2'} obj.foo(); obj.foo.call(obj2); obj.foo2(); obj.foo2.call(obj2); obj.foo3()(); obj.foo3.call(obj2)(); obj.foo3().call(obj2); obj.foo4()(); obj.foo4.call(obj2)(); obj.foo4().call(obj2);
----------------------答案分割线----------------------
//MarlboroKay 隐式绑定 //obj2 显示绑定 > 隐式绑定 //Window foo2为箭头函数,不满足this规则,所以返回window.name //Window 同理 //Window foo3返回一个函数,调用时在全局作用域,所以返回window.name //Window foo3显示绑定到obj2,但是返回的函数调用时在全局作用域,所以返回window.name //obj2 foo3返回函数,显示绑定到obj2,所以返回obj2.name //MarlboroKay foo4返回箭头函数,箭头函数查找上一层作用域,所以返回obj.name //obj2 foo4显示绑定到obj2,并返回一个箭头函数,箭头函数查找上一层作用域,所以返回obj2.name /*MarlboroKay foo4返回的箭头函数显示绑定到obj2,但箭头函数不满足显示绑定规则,仍然查找上一层作用域, 所以返回obj1.name*/
3.3
var name = 'Window'; function GetThisName(name){ this.name = name; this.foo2 = () => console.log(this.name); } GetThisName.prototype.foo = function(){ console.log(this.name); } GetThisName.prototype.foo3 = function(){ return function(){ console.log(this.name); } } GetThisName.prototype.foo4 = function(){ return () => { console.log(this.name); } } let g1 = new GetThisName('g1'); let g2 = new GetThisName('g2'); g1.foo(); g1.foo.call(g2); g1.foo2(); g1.foo2.call(g2); g1.foo3()(); g1.foo3.call(g2)(); g1.foo3().call(g2); g1.foo4()(); g1.foo4.call(g2)(); g1.foo4().call(g2);
----------------------答案分割线----------------------
//g1 隐式绑定 //g2 显示绑定 > 隐式绑定 //g1 foo2返回箭头函数,箭头函数查找上一层作用域,返回g1.name //g1 foo2返回箭头函数,显示绑定不生效,返回g1.name //Window foo3返回一个函数,调用时在全局作用域,所以返回window.name //Window foo3显示绑定到g2,但是返回的函数调用时在全局作用域,所以返回window.name //g2 foo3返回函数,显示绑定到g2,所以返回g2.name //g1 foo4返回箭头函数,箭头函数查找上一层作用域,所以返回g1.name //g2 foo4显示绑定到g2,并返回一个箭头函数,箭头函数查找上一层作用域,所以返回g2.name //g1 foo4返回的箭头函数显示绑定到g2,但箭头函数不满足显示绑定规则, //仍然查找上一层作用域,所以返回g1.name
3.4
var name = 'Window'; function GetThisName(name){ this.name = name; this.obj = { name: 'obj', foo(){ return function(){ console.log(this.name); } }, foo2(){ return () => { console.log(this.name); } }, } } let g1 = new GetThisName('g1'); let g2 = new GetThisName('g2'); g1.obj.foo()(); g1.obj.foo.call(g2)(); g1.obj.foo().call(g2); g1.obj.foo2()(); g1.obj.foo2.call(g2)(); g1.obj.foo2().call(g2);
----------------------答案分割线----------------------
//Window 返回一个函数,默认绑定 //Window 虽然显示绑定到g2,但是函数调用时在全局作用域下,默认绑定 //g2 foo返回的函数,显示绑定到g2,所以返回g2.name //obj foo2返回一个箭头函数,箭头函数调用时,查找上一层作用域,返回obj.name //g2 foo2 显示绑定到g2,箭头函数调用时,查找上一层作用域,返回g2.name //obj foo2返回的箭头函数,显示绑定到g2,但是箭头函数不满足显示绑定规则,查找上一层作用域,返回obj.name
3.5
var num1 = '66'; const obj = { num1: 99, foo(){ console.log(this.num1); function bar(){ console.log(this.num1) } bar(); }, } obj.foo();
----------------------答案分割线----------------------
//99 隐式绑定 //66 默认绑定(bar在调用时,没有显示和隐式绑定,处于全局作用域)
3.6
var num1 = 66; const obj = { num1: 99, foo(){ console.log(this.num1); }, } let bar = obj.foo; const obj2 = { num1: 33, foo:obj.foo, } obj.foo(); //? bar();//? obj2.foo() //?
----------------------答案分割线----------------------
//99 隐式绑定 //66 显示绑定 //33 隐式绑定(this的绑定与定义时无关,与调用时的位置和调用方式有关)
3.7
function foo(val){ this.a = val; return this; } var a = foo(33); var b = foo(66); console.log(a.a); //? console.log(b.a); //?
----------------------答案分割线----------------------
//undefined //66 /* foo(33),相当于默认引用,this指向window,此时this.a 等同于 window.a = 33 return this 等同于 return window 所以,var a = foo(33) 等同于 var a = window 但需要注意,此时var声明的a,属于全局作用域的window下,也就是:window.a = window 如果后续没有其他操作,则此时 console.log(a.a) 为 Window 当 foo(66) 执行时,此时,window.a = 66,return window 同理:window.b = window,b.a 等同于window.a 为66。 所以,console.log(a.a) 等同于 console.log(66.a) === undefined console.log(b.a) 等同于 console.log(window.a) === 66 */
3.8
function foo(){ getName = function(){ console.log(1); } return this; } foo.getName = function(){console.log(2)}; foo.prototype.getName = function(){console.log(3)}; var getName = function(){console.log(4)}; function getName(){console.log(5)}; foo.getName(); //? getName(); //? foo().getName(); //? getName(); //? new foo.getName(); //? new foo().getName(); //? new new foo().getName(); //?
可以先仔细做一下,仔细思考。(答案参考评论区)
热门评论
https://www.mooyuu.com/user/89564/
https://www.mooyuu.com/user/89564/infos.html
https://www.mooyuu.com/user/89564/favs.html
https://my.oschina.net/jjgjgj
https://www.changchenghao.cn/user/2119
2, 4, 1, 1, 2, 3, 3