this绑定规则细节和面试分析
默认绑定
独立函数调用的情况下默认绑定
独立的函数调用我们可以理解成函数没有绑定到某个对象进行调用
【案例】:
function foo() {
console.log(this)
}
foo() // window
function test1() {
console.log(this) // window
test2()
}
function test2() {
console.log(this) // window
test3()
}
function test3() {
console.log(this) // window
}
test1();
function foo(func){
func()
}
var obj = {
name:"why",
bar: function() {
console.log(this) // window
}
}
foo(obj.bar)
隐式绑定
它的调用位置中,是通过某个对象发起的函数调用
【案例】
function foo() {
console.log(this)
}
var obj = {
name:"why",
foo:foo
}
obj.foo() // obj
function foo() {
console.log(this)
}
var obj1 = {
name:"obj1",
foo:foo
}
var obj2 = {
name:"obj2",
obj1:obj1
}
obj2.obj1.foo() // obj1
function foo() {
console.log(this)
}
var obj1 = {
name :"obj1",
foo:foo
}
var bar = obj1.foo()
bar() // window
显示绑定
通过call
或apply
绑定this对象
- 显示绑定后,
this
久会明确的指向绑定的对象
function foo() {
console.log(this)
}
foo.call(window)
foo.call({name:'why'})
foo.call(123)
- 如果我们希望一个函数总是显示得绑定到以一个对象上,可以这样做
function foo() {
console.log(this)
}
var obj = {
name:'why'
}
var bar = foo.bind(obj)
bar() // obj对象
bar() // obj对象
bar() // obj对象
new绑定
- JavaScript中得函数可以当作一个类的构造函数来使用,也就是使用
new
关键字 - 使用
new
关键字来调用函数,会执行如下的操作:- 创建一个全新的对象
- 这个新对象会执行
prototype
连接 - 这个新对象会绑定到函数调用的this上(this的绑定在这个步骤完成)
- 如果函数没有发回其他对象,表达式会返回这个新对象
function Person(name) {
console.log(this)
this.name = name;
}
var p = new Person("why")
console.log(p)
内置函数的绑定
定时器,内部应该采取默认调用
setTimeout(() => {
console.log(this) // window
},1000)
事件回调,内容使用了显示绑定
const boxDiv = document.querySelector('.box')
boxDiv.onclick = function() {
console.log(this)
}
boxDiv.onclick() //boxDiv
数组 forEach/map/filter/find
var name = ["abc","cba","nba"]
names.forEach(function(item){
console.log(item,this) // window
})
我们可以通过传递第二个参数来绑定this
var name = ["abc","cba","nba"]
names.forEach(function(item){
console.log(item,this) // abc
}, "abc")
规则优先级
如果一个函数调用位置应用了多条规则,优先级怎么判断
-
默认规则的优先级最低
- 毫无疑问,默认规则的优先级是最低的,因为存在其他规则时,就会通过其他规则的方式来绑定this
-
显示绑定优先级高于隐式绑定
function foo() { console.log(this) } var obj = { name:"obj", foo:foo.bind("aaa") } obj.foo() // aaa
-
new绑定优先级高于隐式绑定
var obj = { name:"obj", foo:function(){ console.log(this) } } var f = new obj.foo() // foo,它new的是obj.foo
-
new绑定优先级高于显示绑定
// 结论:new关键字不能和apply/call一起来使用 // 我们使用new和bind进行比较 function foo() { console.log(this) } var bar = foo.bind('aaa') var obj = new bar() // foo
this规则之外
忽略显示绑定
apply/call/bind:当传入null/undefined时,自动将this绑定成全局对象
function foo() {
console.log(this)
}
foo.apply(null) // window
foo.apply(undefined) // window
间接函数
另外一种情况,创建一个函数的间接引用,这种情况使用默认绑定规则
var obj1 = {
name:"obj1",
foo:function() {
console.log(this)
}
}
var obj2 = {
name:"obj2"
}; // 切记此处要有分号,不然代码不会被正常识别, 而变成一下情况var obj2 = {name:"obj2"}(obj2.bar = obj1.foo)()
// console.log(obj2.bar = obj1.foo) // function() { console.log(this) }
(obj2.bar = obj1.foo)() // window
以下代码也是没有加分号的问题:
function foo(el) {
console.log(el,this.id)
}
var obj = {
id:"awesome"
} // 加上;即可
[1,2,3].forEach(foo.obj) // Uncaught TypeError: Cannot read properties of undefined (reading 'forEach')
箭头函数
箭头函数是ES6之后增加的一种编写函数的方法,并且它比函数表达式更加简洁
- 箭头函数不会绑定
this
、arguments
属性 - 箭头函数不能作为构造函数来使用(不能和new一起来使用,会抛出错误)
- 箭头函数不适用
this
的四种标准规则,而是根据外层作用域来决定this
即使对箭头函数使用call
、bind
,也无法对其this
进行绑定
var foo = () => {
console.log(this)
}
foo.call('as') // window
面试题
一、
var name = "window";
var person = {
name:"person",
sayName:function() {
console.log(this.name)
}
}
function sayName () {
var sss = person.sayName;
sss(); // window
person.sayName(); // person
(person.sayName)(); //person
(b = person.sayName)() // window, 创建一个函数的间接引用,这种情况使用默认绑定规则
}
sayName()
二、
var name = "window"
var person1 = {
name:"person1",
foo1:function () {
console.log(this.name)
},
foo2:() => console.log(this.name),
foo3:function() {
return function() {
console.log(this.name)
}
},
foo4:function(){
return () => {
console.log(this.name)
}
}
}
var person2 = {name:"person2"}
person1.foo1() // person
person1.foo2.call(person2) //person2
person1.foo2() // window
person1.foo2.call(person2) // window,因为箭头函数不能显示绑定
person1.foo3()() // window
person1.foo3.call(person2)() // window
person.foo3().call(person2) // person2
person1.foo4()() // person1
person1.foo4.call(person2)() // person2
person1.foo4().call(person2) // person1
三、
var name = "window";
function Person(name) {
this.name = name;
this.foo1 = function () {
console.log(this.name);
};
this.foo2 = () => console.log(this.name);
this.foo3 = function () {
return function () {
console.log(this.name);
};
};
this.foo4 = function () {
return () => {
console.log(this.name);
};
};
}
var person1 = new Person("person1");
var person2 = new Person("person2");
person1.foo1(); // person1
person1.foo1.call(person2); // person2
person1.foo2(); // person1
person1.foo2.call(person2); // person1
person1.foo3()(); // window
person1.foo3.call(person2)(); // window
person1.foo3().call(person2); // person2
person1.foo4()(); // person1
person1.foo4.call(person2)(); // person2
person1.foo4().call(person2); // person1