一、规则
- 函数在调用时,js会 默认给this绑定一个值
- this的绑定和 定义的位置(编写的位置)没有关系
- this的绑定和调用方式以及调用的位置有关系
- this是在运行时被绑定的
二、绑定方式
主要分为默认绑定、隐式绑定、显示绑定、new绑定
而js内置函数的绑定规则属于特殊规则,根据不同的函数判断(一般根据经验判断)
1.1 默认绑定
- 没有绑定到任何对象上,独立的函数调用——指向window
- 绑定到对象上,但是独立调用——指向window
- 严格模式下,独立调用指向的是undefined
// 案例1
function foo() {
console.log(this)
}
// 独立函数调用,默认绑定,指向window
foo()
// 案例2
function foo1() {
console.log(this)
}
function foo2() {
console.log(this)
foo1()
}
function foo3() {
console.log(this)
foo2()
}
foo3()
// 案例3
var obj_3 = {
name_3: "yz1",
foo_3: function() {
console.log(this)
}
}
var bar_3 = obj.foo_3
bar_3() // 独立函数调用,window
obj_3.foo_3() // obj调用方式,指向obj
// 案例4
function foo_4() {
console.log(this)
}
var obj_4 = {
name: "yz2",
foo_4: foo_4
}
var bar_4 = obj.foo_4
bar_4() // 独立函数调用,window
// 案例5
function foo_5() {
function bar_5() {
console.log(this)
}
return bar_5
}
var f = foo_5()
f() //独立调用 window
var obj_5 = {
name_5: "yz3",
sleeping: f
}
obj_5.sleeping() // 返回obj对象,隐式绑定
1.2 隐式绑定
- 通过某个对象进行调用——指向相应的对象
// 案例一
function foo1() {
console.log(this)
}
var obj1 = {
name: "yz",
foo1: foo1
}
obj1.foo1() // 返回obj对象,隐式绑定
// 案例二
var obj2 = {
name: "yz",
sleeping: function() {
console.log(this.name + " is sleeping")
},
smiling: function() {
console.log(this)
console.log(this.name + " is smiling")
},
}
obj2.sleeping()
var fn2 = obj2.smiling
fn2() //独立函数调用 window
// 案例三
var obj3_1 = {
name: 'obj_1',
foo3_1: function() {
console.log(this)
}
}
var obj3_2 = {
name: "obj_2",
fn: obj3_1.foo3_1
}
obj3_2.fn() //返回obj2
1.3 显式绑定
- 通过call、apply、bind调用绑定this
function foo() {
console.log(this)
}
// 函数自带一个call函数,执行
foo()
/**
* call/apply调用不同在于this的绑定的不同
* foo指向window
*/
var obj = {
name: "hello"
}
// 显示调用,call/apply可以指定this的绑定对象的
foo.call(obj) // hello
foo.apply(obj) // hello
/**
* call与apply的区别
* 1.参数不同
*/
function sum(num1, num2, num3) {
console.log(num1 + num2 + num3, this)
}
sum.call("call", 20, 30, 40)
sum.apply("apply", [20, 30, 40])
/**
* call与apply可以明确指定this的绑定,实现显示绑定
*/
- 如果绑定的对象为null、undefined, 会转化成默认绑定
-
- 非严格模式:指向window
- 严格模式下:指向null、undefined
function foo() {
console.log("foo", this);
}
foo.apply("abc");
foo.apply(null);
foo.apply(undefined);
1.4 new绑定
- 通过new关键字进行绑定——指向new新生成的对象
function foo() {
console.log('foo函数', this);
this.name = 'yz';
}
/**
* new操作
* 1.创建新的空对象
* 2.将this指向这个空对象
* 3.执行函数体重的代码
* 4.没有显示返回非空对象时,默认返回这个对象
*/
new foo();
1.5 内置函数绑定(一般根据经验)
// 内置函数(第三方库):根据经验
// 1.定时器
setTimeout(function () {
console.log("定时器", this);
}, 1000);
// 2.按钮的点击监听
var btnEl = document.querySelector("button");
btnEl.onclick = function () {
console.log("btn的点击", this);
};
btnEl.addEventListener("click", function () {
console.log("listener", this);
});
// 3.forEach
var names = ["aaa", "bbb"];
names.forEach(function (item) {
console.log("forEach", this);
});
names.forEach(function (item) {
console.log("forEach", this);
}, "aaa");
三、绑定规则的优先级
- 默认绑定优先级最低
- 显式>隐式
var obj = {
name: 'obj',
foo: function() {
console.log(this)
}
}
console.log("直接输出")
obj.foo()
// 1.显示绑定优于隐式绑定
console.log("既有显示绑定也有隐式绑定")
obj.foo.call("aaa") //绑定aaa
// 2.bind 隐式绑定
var bar = obj.foo.bind("bbb")
bar() //绑定bbb
// 3.更明显的比较,bind显示绑定优先级高于隐式绑定
function foo() {
console.log(this)
}
var obj1 = {
name: "obj1",
foo: foo.bind("ccc")
}
obj1.foo() //绑定ccc
3. new绑定>隐式
// new 绑定高于隐式绑定
var obj = {
name: "obj",
foo: function() {
console.log(this)
}
}
var f = new obj.foo() // 绑定foo对象
4. new绑定>bind绑定(new不能和apply、call一起使用)
// new优先级高于显示绑定
function foo1() {
console.log(this)
}
var bar = foo1.bind("aaa")
var ff = new bar() // 绑定foo1对象
5. bind>apply/call 6. 总结: new>显示(bind>apply/call)>隐式>默认
function foo() {
console.log("foo", this);
}
// 1.显示绑定高于隐式绑定
// 1.1 apply
var obj = { foo };
obj.foo.apply("abc");
// 1.2 bind
var bar = foo.bind("aaa");
var obj = {
name: "yz",
baz: bar,
};
obj.baz();
// 2.new绑定优先级高于隐式绑定
var obj2 = {
name: "yz",
age: 18,
foo: function () {
console.log("foo", this);
},
};
new obj2.foo();
// 3.new/显示
// 3.1 new不可以和apply/call一起使用
// 3.2 new优先级高于bind
var bindFn = foo.bind("aaa");
new bindFn();
// 4.bind/apply优先级
bindFn.apply("apply");
bindFn.call("call");
四、箭头函数
4.1 与普通函数的区别
- 不会绑定this、arguments属性
- 不能作为构造函数来使用( 不能和new一起使用 )
4.2 优化代码规则
- 果箭头函数只有一个参数,那么()可以省略
- 如果函数体重只有一行执行代码,那么{}可以省略
- 只有一行代码时,这行代码的表达式结果会作为函数的返回值默认返回
- 如果默认返回值是一个对象,那么这个对象必须加()
var names = ["aaa", "bbb", "ccc"];
// 优化一:如果箭头函数只有一个参数,那么()可以省略
names.forEach((item, index, arr) => {
console.log(item, index, arr);
});
names.forEach((item) => {
console.log(item);
});
// 优化二:如果函数体重只有一行执行代码,那么{}可以省略
// 一行代码中不能带return
names.forEach((item) => console.log(item));
// 优化三:只有一行代码时,这行代码的表达式结果会作为函数的返回值默认返回
var nums = [2, 3, 6, 9, 8, 7, 4, 1, 5];
var newNum = nums.filter((item) => item % 2 === 0);
console.log(newNum);
// 优化四:如果默认返回值是一个对象,那么这个对象必须加()
// 注意:react中redux经常使用
// var arrFn = () => [123, "abc"];
var arrFn = () => ({ name: "yz" });
console.log(arrFn());
4.3 this绑定
- 箭头函数中 没有this ,因此输出this的时候,会去 上层作用域 去找
var bar = () => {
console.log("bar", this);
};
bar();
// 通过apply调用,也是没有this
bar.apply("aaa"); //window
- this的查找规则
var obj = {
name: "obj",
foo: function () {
// foo函数内部有this
var bar = () => {
// bar函数内部没有this
console.log("bar", this);
};
return bar;
},
};
var fn = obj.foo();
fn.apply("bbb"); // obj
var message = "global message";
var obj2 = {
name: "obj",
message: "obj2 message",
foo: () => {
// foo函数内部有this
var bar = () => {
// bar函数内部没有this
console.log("bar", this);
console.log(message);
};
return bar;
},
};
var fn2 = obj2.foo();
fn2.apply("ccc"); // window
五、关于this的面试题
面试题一
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(); // person1
person1.foo1.call(person2); // person2
person1.foo2(); // window
person1.foo2.call(person2); // window
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
面试题三
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
面试题四
var name = "window";
function Person(name) {
this.name = name;
this.obj = {
name: "obj",
foo1: function () {
return function () {
console.log(this.name);
};
},
foo2: function () {
return () => {
console.log(this.name);
};
},
};
}
var person1 = new Person("person1");
var person2 = new Person("person2");
person1.obj.foo1()(); // window
person1.obj.foo1.call(person2)(); // window
person1.obj.foo1().call(person2); // person2
person1.obj.foo2()(); // obj
person1.obj.foo2.call(person2)(); // person2
person1.obj.foo2().call(person2); // obj