文章来源:http://cherryblog.site/call-apply-bind-this.html#more
this的指向
this的用法是经典的面试问题,不了解本质原理,一不小心就会入坑,所以在接下来我们深入其本质,探寻this的用法,重要的是记住this永远指向最后调用它的那个对象。
下面看一些简单的例子:
ex1:
var name = "windowsName";
function a() {
var name = "Cherry";
console.log(this.name);// windowsName
console.log("inner:" + this);// inner: Window
}
a();
console.log("outer:" + this)
从上面的例子中我们能看到最后调用函数a的地方是a(),前面没有调用的对象,所以就是全局对象window调用的a(),相当于window.a(),所以在执行函数a的过程中,this的指向是window。
ex2:
var name = "windowsName";
var a = {
name: "Cherry",
fn : function () {
console.log(this.name); // Cherry
}
}
a.fn();
fn()被a调用。所以在执行fn的过程中this指向a,this.name就等于Cherry。
ex3:
var name = "windowsName";
var a = {
// name: "Cherry",
fn : function () {
console.log(this.name); // undefined
}
}
window.a.fn();
fn()被对象a调用,但是对象a并没有定义name,所以this.name等于undefined,即使window中有name属性,也不会继续向上一个对象寻找,而是直接输出。
ex4:
var name = "windowsName";
var a = {
name : null,
// name: "Cherry",
fn : function () {
console.log(this.name);// windowsName
}
}
var f = a.fn;
f();
对象a的fn方法直接赋给了f,但是没有被调用,而是由window调用了f(),所以在执行fn()的过程中,this指向的是window。
从上面4个例子可以看出,this的指向并不是在创建的时候就可以确定的,永远是this永远指向最后调用它的那个对象。
怎么改变this的指向
有以下几种方法:
1、使用箭头函数
2、在函数内部使用:that=this
3、使用apply、call、bind
4、new实例化一个对象
ex5:
var name = "windowsName";
var a = {
name : "Cherry",
func1: function () {
console.log(this.name)
},
func2: function () {
setTimeout( function () {
this.func1()
},100);
}
};
a.func2() // this.func1 is not a function
该例子会报错,因为最后调用setTimeout的对象是window,但是在window中没有func1函数。
对上面的例子进行改造:
箭头函数
箭头函数的this始终指向函数定义时的this,而非执行时。箭头函数中没有this绑定,必须通过查找作用域链来决定其值。如果箭头函数被非箭头函数包含,则this绑定的是最近一层非箭头函数的this,否则this为undefined。
ex6:
var name = "windowsName";
var a = {
name : "Cherry",
func1: function () {
console.log(this.name)
},
func2: function () {
setTimeout( () => {
this.func1()
},100);
}
};
a.func2() // Cherry
在函数内部使用that=this
我们最先将调用这个函数的对象报保存在变量that中,然后在函数中使用这个that,这样that就不会改变。
var name = "windowsName";
var a = {
name : "Cherry",
func1: function () {
console.log(this.name)
},
func2: function () {
var _this = this;
setTimeout( function() {
_this.func1()
},100);
}
};
a.func2() // Cherry
首先设置that=this,这里的this是调用func2的对象a,为了防止在func2中的setTimeout被window调用,将this赋值给一个变量that,这样在func2中我们使用的that就是指向对象a。
使用apply、call、bind
var a = {
name : "Cherry",
func1: function () {
console.log(this.name)
},
func2: function () {
setTimeout( function () {
this.func1()
}.apply(a),100);
}
};
a.func2()
var a = {
name : "Cherry",
func1: function () {
console.log(this.name)
},
func2: function () {
setTimeout( function () {
this.func1()
}.call(a),100);
}
};
a.func2()
var a = {
name : "Cherry",
func1: function () {
console.log(this.name)
},
func2: function () {
setTimeout( function () {
this.func1()
}.bind(a),100);
}
};
a.func2()
apply、call、bind 区别
在MDN中定义apply如下:
apply()方法调用一个函数,其具有一个指定的this值,以及作为一个数组提供的参数。
语法:
fun.apply(thisArg,[argsArray])
thisArg: 在fun函数运行时指定的this值。需要注意的是,指定的this值不一定是该函数执行时真正的this值,如果这个函数处于非严格模式下,则指定为null或undefined时会自动指向全局对象,同时值为原始值的this会指向该原始值的自动包装对象。
argsArray:一个数组或类数组对象,其中的数组元素将作为单独的参数传给fun函数。如果改参数的值为null或undefined,则表示不需要传入任何参数。
apply和call区别是call方法接受的是若干个参数列表,而apply接受的是一个包含多个参数的数组。
var a ={
name : "Cherry",
fn : function (a,b) {
console.log( a + b)
}
}
var b = a.fn;
b.apply(a,[1,2]);
var a ={
name : "Cherry",
fn : function (a,b) {
console.log( a + b)
}
}
var b = a.fn;
b.apply(a,1,2);
bind()方法创建一个新的函数,当被调用时,将其this关键字设置为提供的值,在调用新函数时,在任何提供之前将提供一个给定的参数序列。我们必须手动调用。
var a ={
name : "Cherry",
fn : function (a,b) {
console.log( a + b)
}
}
var b = a.fn;
b.bind(a,1,2)()
JS中的函数调用
函数调用的方法一共有四种:
1、作为一个函数调用
2、函数作为方法调用
3、使用构造函数调用函数
4、作为函数方法调用函数
作为一个函数调用
var name = "windowsName";
function a() {
var name = "Cherry";
console.log(this.name); // windowsName
console.log("inner:" + this); // inner: Window
}
a();
console.log("outer:" + this) // outer: Window
这样一个最简单的函数,不属于任何一个对象,就是一个函数,在浏览器的非严格模式下默认是属于window的,在严格模式下就是undefined。
函数作为方法调用
var name = "windowsName";
var a = {
name: "Cherry",
fn : function () {
console.log(this.name); // Cherry
}
}
a.fn();
使用构造函数调用函数
// 构造函数:
function myFunction(arg1, arg2) {
this.firstName = arg1;
this.lastName = arg2;
}
// This creates a new object
var a = new myFunction("Li","Cherry");
a.lastName;
这里就要说另一个经典的面试题:new的实例化过程
伪代码表示:
var a=new myFunction("li","Cherry");
new myFunction(){
var obj={};
obj._proto_=myFunction.prototype;
var result=myFunction.call(obj,"li","Cherry");
return typeof result==='obj'?result:obj;
}
1、创建了一个空对象obj
2、将创建的空对象的隐式原型指向其构造函数的显示原型
3、使用call改变this的指向
4、如果无返回值或者返回一个非对象值,则将obj返回作为新对象;如果返回值是一个新对象的话那么直接返回该对象。