再说this
在之前的文章中讲了this指针的绑定方式—JavaScript之this指针,这次通过几道题来巩固一下前面所学的内容
this指针绑定分为默认绑定、隐式绑定、显式绑定(call、apply、bind)、new绑定。并且其优先级为 new绑定 > 显式绑定(bind) > 隐式绑定 > 默认绑定
默认绑定
默认绑定就是函数调用时的this默认绑定window,箭头函数除外,函数可以访问window下的全局变量。
但是也有例外情况:
- 在非严格模式下,函数中的this是指向的是window
- 在严格模式下,函数中的this指向的是undefined,但是全局中的this还是指向window
- 使用let、const来定义变量并不会绑定到window中
//非严格模式下,函数中的this指向window
var a = 1;
function bar(){
console.log(this); //window
console.log(this === window); //true
console.log(this.a); //1
}
bar();
console.log(this); //window
console.log(window); //window
console.log(this.a); //1
console.log(window.a); //1
//严格模式下,函数中的this指向undefined
'use strict';
var a = 1;
function bar(){
console.log(this); //undefined
console.log(this === window); //false
console.log(this.a); //报错
}
//全局中的this还是指向window
console.log(this); //window
console.log(window); //window
console.log(this.a); //1
console.log(window.a); //1
bar();
//使用let或const来定义变量,window找到不到该变量,因此为undefined
let a = 1;
function bar(){
console.log(this); //window
console.log(this === window); //true
console.log(this.a); //undefined
}
bar();
console.log(this); //window
console.log(window); //window
console.log(this.a); //undefined
console.log(window.a); //undefined
函数不管套了几层,只要不是箭头函数,默认绑定的this始终是window
//由于函数中的this指向的是window,因此this.a还是全局作用域下的a,但在函数内部直接打印a的话,是使用内部定义的a,因为作用域会由里向外查找,当在内部作用域中找打a时,就停止查找了
var a = 1;
function bar(){
var a = 11;
console.log(a); //11
console.log(this.a); //1
//普通函数调用时默认指向始终指向window的,因此inner函数中的this还是window
function inner(){
//直接打印a的话,由于inner中没有d定义a,所以会在bar函数的作用域中查找,找到后就停止查找了,因此输出的还是11
console.log(a); //11
console.log(this.a); //1
}
inner();
}
bar();
隐式绑定
隐式绑定是指若有对象调用函数,函数中的this就会隐式的绑定到对象上。
隐式绑定的规则是:this始终指向函数被调用前的最近的对象。因为可能有嵌套子对象,这时哪个对象最后调用函数,那函数就指向谁。
function foo() {
console.log(this.name); // obj1
}
var obj1 = {
name: "obj1",
foo: foo
};
var obj2 = {
name: "obj2",
obj1: obj1
};
obj2.obj1.foo(); //obj1,因此obj1对象是最后调用的
隐式绑定也会出现一些问题:
- 在函数赋值给变量时,会丢失函数原来绑定对象
//将函数赋值给另一个变量时就会丢失绑定对象
//通过赋值方式获取到的函数将会丢失原来的this指针
function foo () {
console.log(this.a)
}
var obj = { a: 1, foo };
var a = 2;
var foo2 = obj.foo;
obj.foo(); //1
foo2(); //2 foo绑定的obj对象已丢失,因此foo2中的this指向window
- 将函数作为参数传递时也会丢失绑定对象
function foo(){
console.log(this);
console.log(this.a);
}
function callBack(fun){
//var fun = fun; //其实已经隐式的做了一次变量赋值
console.log(this);
fun();
}
var obj = {a:1, foo: foo}
var a = 11;
//回调函数obj.foo的this指针指向window,不是因为callBack指向window,而是其本身指向window
callBack(obj.foo); //依次打印window, window,11
var obj2 = {a: 111, callBack: callBack}
//回调函数内的参数函数中的this与回调函数没有关系,两者的this互不影响,参数函数中的this指向方式与默认绑定规则一致
obj2.callBack(obj.foo); //依次打印obj2对象、window、11
因此,当隐式绑定时,函数赋值给另一个变量会丢失原来绑定对象,或函数作为参数传入另一个函数中时也会丢失原来对象,并且this指向遵循默认绑定规则
显式绑定
显式绑定是通过call、apply、bind来强行改变函数的this指针
- call接收一个参数列表,apply接收一个参数数组
- 使用call和apply,函数会直接执行,而bind是先创建一个函数,调用之后才会执行
function foo () {
console.log(this.a)
}
var obj = { a: 1 };
var a = 11;
foo(); //11
foo.call(obj); //1
foo.apply(obj); //1
foo.bind(obj) //不会执行,只是创建了一个函数,没有调用
//foo.bind(obj)(); //这样才会执行
var obj1 = {
a: 1
};
var obj2 = {
a: 2,
foo1: function () {
console.log(this.a); //2
},
foo2: function () {
function inner () {
console.log(this); //window
console.log(this.a); //3
}
inner(); //inner函数依次打印window,3
inner.call(obj1); //inner函数依次打印obj1对象,1
}
};
var a = 3;
obj2.foo1();
obj2.foo2();
//笔试题1
function foo () {
console.log(this.a)
}
var obj = { a: 1, foo };
var a = 2;
var foo2 = obj.foo;
var obj2 = { a: 3, foo2: obj.foo }
obj.foo(); //1,隐式转换
foo2(); //2,发生了隐式丢失,这是this指向window
obj2.foo2(); //3,发生了隐式丢失,然后发生了隐式转化,this指向obj2
//笔试题2
function foo () {
console.log(this.a)
}
var obj = { a: 1 };
var a = 2;
foo(); //2
foo.call(obj); //1
foo().call(obj); //2 报错 先执行foo()打印出2,然后call报错,因为foo()的返回值不是一个函数了,若foo()返回一个函数,将会绑定到obj
//笔试题3
function foo () {
console.log(this.a);
return function () {
console.log(this.a);
}
}
var obj = { a: 1 };
var a = 2;
//bind()函数是创建了一个新函数,但是并没有执行该函数
foo(); //2
foo.bind(obj); //定义函数但未执行
foo().bind(obj); //2 ——foo()执行了,但是后面的绑定函数未执行
//笔试题4
function foo () {
console.log(this.a)
returnfunction () {
console.log(this.a)
}
}
var obj = { a: 1 };
var a = 2;
//先执行foo.call(obj)输出1,并返回一个匿名函数,然后再执行匿名函数,此时匿名函数是指向window的
foo.call(obj)(); //先输出1,然后是2
//题5
var obj = {
a: 'obj',
foo: function () {
console.log('foo:', this.a)
return function () {
console.log('inner:', this.a)
}
}
};
var a = 'window';
var obj2 = { a: 'obj2' };
obj.foo()(); //foo:obj foo:window
obj.foo.call(obj2)(); //foo:obj2 foo:window
obj.foo().call(obj2); //foo: obj foo:obj2
//题6
function foo() {
console.log(this.a);
}
var a = 1;
var obj1 = {
a: 2,
};
var obj2 = {
a:3,
};
//call、apply、bind一旦使用,this就不再改变,但是这里好像不行,还有待研究
foo(); //1
foo.call(obj1); //2
foo.call(obj2); //3
new绑定
new绑定会构造一个新对象并把这个新对象绑定到调用函数中的this
//使用new来调用Person,构造了一个新对象person,并把person绑定到Person调用中的this
function Person (name) {
this.name = name;
}
var name = 'window';
var persion = new Person('simon');
console.log(person1.name); //simon
//其实原理与上面的例子是一样的当函数返回值为一个函数时,这是返回函数this指向window
function Person (name) {
this.name = name;
this.foo1 = function () {
console.log(this.name);
};
this.foo2 = function () {
return function () {
console.log(this.name);
}
}
}
var name = 'window';
var person1 = new Person('object');
person1.foo1(); //object
person1.foo2()(); //window
箭头函数
箭头函数是ES6的新函数类型,与普通函数不同的是,箭头函数本身没有this指针的,必须通过查找作用域来决定,如果箭头函数被非箭头函数包含,则this绑定的是最近一层非箭头函数的this,否则,this为undefined。并且,
是指向函数定义时的this而不是执行时的
var obj = {
name: 'obj',
foo1: () => {
console.log(this.name)
},
foo2: function () {
console.log(this.name)
return ()=> {
console.log(this.name)
}
}
};
var name = 'window';
obj.foo1(); //window
obj.foo2()(); //obj obj
//this由外层作用域决定,且指向函数定义时的this而非执行时
var name = 'window';
function Person (name) {
//字面量写法
this.name = name
this.foo1 = function () {
console.log(this.name);
}
this.foo2 = () => {
console.log(this.name);
}
}
var person2 = {
name: 'person2',
foo2: () => {
console.log(this.name)
}
}
var person1 = new Person('person1');
person1.foo1(); //person1
person1.foo2(); //person1,因为new Person后,person1的this指向了Person了
person2.foo2(); //window,没有使用new操作符,因为箭头函数this向外查找,找到window
//使用call、apply、bind无法改变箭头函数的this指向,但可以改变作用域中的this来间接修改
var name = 'window';
var obj1 = {
name: 'obj1',
foo1: function () {
console.log(this.name);
return() => {
console.log(this.name);
}
},
foo2: () => {
console.log(this.name);
return function () {
console.log(this.name);
}
}
}
var obj2 = {
name: 'obj2',
}
obj1.foo1.call(obj2)(); //obj2 obj2-call改变了foo1的this指向,内部的箭头函数也会跟着改
obj1.foo1().call(obj2); //obj1 obj1-call无法改变箭头函数的this指向
obj1.foo2.call(obj2)(); //window window-call函数对箭头函数无效
obj1.foo2().call(obj2); //window obj2-foo2箭头函数内部是一个普通函数,因此能够改变this指向
总结:
- 它里面的
this
是由外层作用域来决定的,且指向函数定义时的this
而非执行时- 字面量创建的对象,作用域是
window
,如果里面有箭头函数属性的话,this
指向的是window
- 构造函数创建的对象,作用域是可以理解为是这个构造函数,且这个构造函数的
this
是指向新建的对象的,因此this
指向这个对象- 箭头函数的
this
是无法通过bind、call、apply
来直接修改,但是可以通过改变作用域中this
的指向来间接修改
综合题
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'-无法改变箭头函数this指向
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'-普通函数this始终指向window
person1.obj.foo1.call(person2)() // 'window'-最后也是一个普通函数
person1.obj.foo1().call(person2) // 'person2'-改变了this的指向
person1.obj.foo2()() // 'obj'-根据外部作用域的this指向来决定箭头函数的this
person1.obj.foo2.call(person2)() // 'person2'
person1.obj.foo2().call(person2) // 'obj'
参考链接: https://mp.weixin.qq.com/s/YhV6A8dsU_sAyjfZTJevIQ