this介绍
this在JavaScript中是一个关键字,用来代表上下文中的一个对象。因此this就是指向一个对象。
但是在不同的作用域中,this的指向就不同,在全局作用域中,this指向window
console.log(this); //window
var name = 'simon';
console.log(this.name); //simon
console.log(window.name); //simon
函数在不同的作用域下调用都会绑定不同的this对象
function foo(){
console.log(this);
}
//直接调用
foo(); //window
//放到一个对象中,再调用
let obj = {
name:'simon',
foo: foo,
}
obj.foo(); //obj对象
//通过call/apply调用
foo.call('abc'); //String{'abc'}对象
上述代码中,foo函数在调用时,会默认给this绑定一个对象,并且this是在函数调用时被绑定的
this的绑定规则
默认绑定
当函数只是当做普通函数调用时,函数的this默认绑定window,箭头函数除外。
function foo(){
console.log(this);
}
foo(); //window
function foo(func) {
func()
}
var obj = {
name: 'simon',
bar: function() {
console.log(this); // window
}
};
foo(obj.bar);
//结果依然是window,是因为在真正函数调用的位置,并没有任何对象的绑定,只是一个普通函数的调用
隐式绑定
function foo() {
console.log(this); // obj对象
}
var obj = {
name: "simon",
foo: foo
}
obj.foo(); //obj1
//foo是通过obj对象调用的,因此this会隐式被绑定到obj对象上
function foo() {
console.log(this); // obj1对象
}
var obj1 = {
name: "obj1",
foo: foo
};
var obj2 = {
name: "obj2",
obj1: obj1
};
obj2.obj1.foo(); //obj1,obj1最后调用foo函数
//通过obj2调用了obj1对象,然后obj1对象调用了foo函数,因此foo函数的this还是指向obj1
//函数的this指向的是.操作符前的对象
function foo() {
console.log(this); //window
}
var obj1 = {
name: "obj1",
foo: foo
}
var bar = obj1.foo;
bar();
//obj1.foo是在bar赋值的时候被调用,但是bar在调用时没有绑定任何对象,因此bar函数中的this是window
显式绑定
通过call/apply方法直接给函数绑定对象
function foo(){
console.log(this);
}
foo.call(window); //window
foo.call({name:'simon'}); //{name:'simon'}
foo.apply(['aaa','bbb']); //["aaa", "bbb"]
通过bind函数来绑定
//自定义bind函数
function foo(){
console.log(this);
}
let obj = {
name: 'simon'
};
//自定义bind函数
function bind(fun, obj){
return function(){
return fun.apply(obj, arguments);
}
}
let bar = bind(foo,obj);
bar(); //obj对象
//内置bind函数
function foo(){
console.log(this);
}
let obj = {
name: 'simon'
};
let bar = foo.bind(obj);
bar(); //obj对象
JS原生函数的this绑定规则
setTimeout函数会传入一个函数,这个函数中的this是window。
由于setTimeout函数内部是通过apply进行绑定this对象的,并且绑定的是全局对象
setTimeout(function(){
console.log(this); //window
}, 500);
forEach函数中传入的函数的this也是window对象
let names = ['simon','simoner','jack'];
names.forEach(function(item){
console.log(this); //输出三次window
});
//改变forEach内部函数的this指向
let names = ['simon','simoner','jack'];
let obj = {name:'simon'};
names.forEach(function(item){
console.log(this); //输出三次{name: "simmon"}
},obj);
new绑定
new关键字来创建对象时,会执行以下步骤
- 创建一个新的对象
- 将构造函数的作用域赋给新对象(因此this就指向了这个对象)
- 执行构造函数中的代码(为这个新对象添加属性)
- 返回新对象
//实现new操作符
function create(){
//创建一个新的对象
let obj = {};
//将构造函数的作用域赋给新对象
let con = [].shift.call(arguments);
obj.__proto__ = con.prototype;
//执行构造函数中的代码
let result = con.apply(obj, arguments);
//返回新的对象
return typeof result === 'object' ? result : obj;
}
//test
function Person(){
this.name = 'simon';
this.age = 24;
this.job = 'student';
}
let person = create(Person);
console.log(person); //Person {name: "simon", age: 24, job: "student"}
function Person(){
console.log(this); //Person()
this.name = name;
}
const person = new Person();
console.log(p); //Person()
绑定规则的优先级
this的绑定规则有多种,当对一个函数使用两种或两种以上规则的时候就会产生冲突。因此需要判断哪种规则的优先级更高
- 默认规则的优先级最低
- 显式绑定优先级高于隐式绑定
- new绑定优先级高于隐式绑定
- new绑定优先级高于bind,new和call、apply不允许同时使用
new绑定 > 显式绑定(bind) > 隐式绑定 > 默认绑定
//显式绑定优先级高于隐式绑定
function foo(){
console.log(this)
}
var obj1 = {
name: "obj1",
foo: foo
}
var obj2 = {
name: "obj2",
foo: foo
}
// 隐式绑定
obj1.foo(); // obj1
obj2.foo(); // obj2
// 隐式绑定和显示绑定同时存在
obj1.foo.call(obj2); // obj2, 说明隐式绑定优先级更高
//new绑定优先级高于隐式绑定
function foo() {
console.log(this);
}
var obj = {
name: "simon",
foo: foo
}
new obj.foo(); // foo对象, 说明new绑定优先级更高
//new和call、apply不允许同时使用
function foo() {
console.log(this);
}
var obj = {
name: "obj"
}
var foo = new foo.call(obj); //将报错,无法同时使用
//new绑定优先级高于bind
function foo() {
console.log(this);
}
var obj = {
name: "obj"
}
var bar = foo.bind(obj);
var foo = new bar(); //输出foo, 说明使用的是new绑定
this番外
忽略显式绑定
在显式绑定时,可以传入null或undefined来忽略显式绑定,这时将使用默认绑定
function foo(){
console.log(this);
}
let obj = {
name: 'simon',
};
foo.call(obj); //obj对象
foo.call(null); //window
foo.call(undefined); //window
let bar = foo.bind(null);
bar(); //window
间接函数引用
当创建一个函数的间接引用时,将使用默认绑定规则
function foo() {
console.log(this);
}
var obj1 = {
name: "obj1",
foo: foo
};
var obj2 = {
name: "obj2"
};
obj1.foo(); // obj1对象
(obj2.foo = obj1.foo)(); // window
//(obj2.foo = obj1.foo)的结果是foo函数,因此foo函数被直接调用,所以是默认绑定
箭头函数
箭头函数是ES6中新增的函数类型。箭头函数与普通函数的区别是箭头函数不会绑定this,而是根据外层作用域来决定this。
//箭头函数不适用于所有规则
let obj = {
foo: ()=>{console.log(this);}
};
obj.foo(); //window
obj.foo.call(obj); //window
var obj = {
data: [],
getData: function() {
var _this = this;
setTimeout(function() {
// 模拟获取到的数据
var res = ["abc", "cba", "nba"];
_this.data.push(...res);
}, 1000);
}
}
obj.getData();
//在setTimeout函数中直接拿到的this是window,因此需要在外层定义this才是对象中的this
//箭头函数写法
//由于箭头函数没有绑定this,因此this就会从从上层作用域中找,直到找到为止
var obj = {
data: [],
getData: function() {
setTimeout(() => {
// 模拟获取到的数据
var res = ["abc", "cba", "nba"];
this.data.push(...res);
}, 1000);
}
}
obj.getData();
//如下面所示,getData也是一个箭头函数,那将继续从上层作用域找,直到找到了window
var obj = {
data: [],
getData: () => {
setTimeout(() => {
console.log(this); // window
}, 1000);
}
}
obj.getData();
//箭头函数向外查找this,找的是外层作用域中的this,由于obj的this是window,所以箭头函数的this也是window。
参考链接: https://mp.weixin.qq.com/s/hYm0JgBI25grNG_2sCRlTA