JavaScript this綁定
理解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() //Windowfoo最终被调用的位置是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) // MarlboroKay1.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 constructornew绑定和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) //Window2.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(); //? 可以先仔细做一下,仔细思考。(答案参考评论区)
共同學習,寫下你的評論
評論加載中...
作者其他優質文章