this 的指向
在 ES5 中,其实 this 的指向,始终坚持一个原理: this 永远指向最后调用它的那个对象。
例子1:
var name = "windowsName";
function a() {
var name = "Cherry";
console.log(this.name);
console.log("inner:" + this);
}
a();
console.log("outer:" + this)
例子2:
var name = "windowsName";
var a = {
name: "Cherry",
fn : function () {
console.log(this.name);
}
}
a.fn();
例子3:
var name = "windowsName";
var a = {
name: "Cherry",
fn : function () {
console.log(this.name);
}
}
window.a.fn();
例子4:
var name = "windowsName";
var a = {
// name: "Cherry",
fn : function () {
console.log(this.name);
}
}
window.a.fn();
例子5:
var name = "windowsName";
var a = {
name : null,
// name: "Cherry",
fn : function () {
console.log(this.name);
}
}
var f = a.fn;
f();
例子6:
var name = "windowsName";
function fn() {
var name = 'Cherry';
innerFunction();
function innerFunction() {
console.log(this.name);
}
}
fn()
如何改变this指向
- 使用ES6箭头函数
- 函数内部使用 that=this
- 使用 apply、call、bind
- new实例化对象
例子7:
var name = "windowsName";
var a = {
name : "Cherry",
func1: function () {
console.log(this.name)
},
func2: function () {
setTimeout( function () {
this.func1()
},100);
}
};
a.func2()
最后调用 setTimeout
的对象是 window,但是在 window 中并没有 func1 函数。
箭头函数
ES6 的箭头函数是可以避免 ES5 中 this 的指向问题,箭头函数的 this 始终指向函数定义时的 this,而非执行时。,箭头函数需要记着这句话:“箭头函数中是没有 this 绑定,必须通过查找作用域链来决定其值,如果箭头函数被非箭头函数包含,则 this 绑定的是最近一层非箭头函数的 this,否则,this 为 undefined”。
例子8:
var name = "windowsName";
var a = {
name : "Cherry",
func1: function () {
console.log(this.name)
},
func2: function () {
setTimeout( () => {
this.func1()
},100);
}
};
a.func2()
在函数内部使用 that = this
例子9:
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()
这里的 this
是调用 func2
的对象 a,为了防止在 func2
中的 setTimeout 被 window 调用而导致的在 setTimeout 中的 this 为 window。我们将 this(指向变量 a)
赋值给一个变量 _this
,这样,在 func2
中我们使用 _this
就是指向对象 a 了。
使用 call、apply、bind
var name = "windowsName";
var a = {
name : "Cherry",
func1: function () {
console.log(this.name)
},
func2: function () {
setTimeout( function () {
this.func1()
}.apply(a),100); //call(a),100); //bind(a)(),100);
}
};
a.func2()
call:
function.call(thisArg, arg1, arg2, ...)
1.call 可以接受多个参数,第一个参数是this 指向的对象,之后的是函数回调所需的入参
function add(a, b) {
return a + b;
}
add.call(this, 1, 2) // 3
2.call 方法可以指定this 的指向(即函数执行时所在的的作用域),然后再指定的作用域中,执行函数
var obj = {};
var f = function(){
return this;
};
console.log(f() === window); // this 指向window
console.log(f.call(obj) === obj) 改变this 指向 obj
3.call 方法的参数,应该是对象obj,如果参数为空或null,undefind,则默认传参全局对象
var n = 123;
var obj = { n: 456 };
function a() {
console.log(this.n);
}
a.call() // 123
a.call(null) // 123
a.call(undefined) // 123
a.call(window) // 123
a.call(obj) // 456
4.call
方法的一个应用是调用对象的原生方法。
var obj = {};
obj.hasOwnProperty('toString') // false
// 覆盖掉继承的 hasOwnProperty 方法
obj.hasOwnProperty = function () {
return true;
};
obj.hasOwnProperty('toString') // true
Object.prototype.hasOwnProperty.call(obj, 'toString') // false
hasOwnProperty
是obj
对象继承的方法,如果这个方法一旦被覆盖,就不会得到正确结果。call
方法可以解决这个问题,它将hasOwnProperty
方法的原始定义放到obj
对象上执行,这样无论obj
上有没有同名方法,都不会影响结果。
apply:
apply
方法的作用与call
方法类似,也是改变this
指向,然后再调用该函数。唯一的区别就是,它接收一个数组作为函数执行时的参数,使用格式如下。
func.apply(thisArg, [arg1, arg2, ...])
apply
方法的第一个参数也是this
所要指向的那个对象,如果设为null
或undefined
,则等同于指定全局对象。第二个参数则是一个数组,该数组的所有成员依次作为参数,传入原函数。原函数的参数,在call
方法中必须一个个添加,但是在apply
方法中,必须以数组形式添加。
function f(x, y){
console.log(x + y);
}
f.call(null, 1, 1) // 2
f.apply(null, [1, 1]) // 2
f
函数本来接受两个参数,使用apply
方法以后,就变成可以接受一个数组作为参数
(1)找出数组最大元素
JavaScript 不提供找出数组最大元素的函数。结合使用apply
方法和Math.max
方法,就可以返回数组的最大元素。
var a = [10, 2, 4, 15, 9];
Math.max.apply(null, a)
(2)将数组的空元素变为undefined
空元素与undefined
的差别在于,数组的forEach
方法会跳过空元素,但是不会跳过undefined
。因此,遍历内部元素的时候,会得到不同的结果。
var a = ['a', , 'b'];
function print(i) {
console.log(i);
}
a.forEach(print)
// a
// b
Array.apply(null, a).forEach(print)
// a
// undefined
// b
(3)转换类似数组的对象
另外,利用数组对象的slice
方法,可以将一个类似数组的对象(比如arguments
对象)转为真正的数组。
Array.prototype.slice.apply({0: 1, length: 1}) // [1]
Array.prototype.slice.apply({0: 1}) // []
Array.prototype.slice.apply({0: 1, length: 2}) // [1, undefined]
Array.prototype.slice.apply({length: 1}) // [undefined]
注:对象必须有length
属性,以及相对应的数字键。
bind:
bind
方法用于将函数体内的this
绑定到某个对象,然后返回一个新函数。(返回一个原函数的拷贝,并拥有指定的 this
值和初始参数)
function.bind(thisArg[, arg1[, arg2[, ...]]])
如果bind
方法的第一个参数是null
或undefined
,等于将this
绑定到全局对象,函数运行时this
指向顶层对象(浏览器为window
)。
例1:
var d = new Date();
d.getTime() // 1481869925657
var print = d.getTime;
print() // Uncaught TypeError: this is not a Date object.
上述代码,我们将d.getTime
方法赋给变量print
,然后调用print
就报错了。这是因为getTime
方法内部的this
,绑定Date
对象的
实例,赋给变量print
以后,内部的this
已经不指向Date
对象的实例了。
例2:
我们使用bind
方法可以解决这个问题:
var print = d.getTime.bind(d);
print() // 1481869925657
例3:绑定自身对象
var counter = {
count: 0,
inc: function () {
this.count++;
}
};
var func = counter.inc.bind(counter);
func();
counter.count // 1
上面代码中,counter.inc
方法被赋值给变量func
。这时必须用bind
方法将inc
内部的this
,绑定到counter
,否则就会出错。
例4:绑定其它对象
var counter = {
count: 0,
inc: function () {
this.count++;
}
};
var obj = {
count: 100
};
var func = counter.inc.bind(obj);
func();
obj.count // 101
上面代码中,bind
方法将inc
方法内部的this
,绑定到obj
对象。结果调用func
函数以后,递增的就是obj
内部的count
属性。
例5
bind
方法每运行一次,就返回一个新函数,这会产生一些问题。比如,监听事件的时候,click
事件绑定bind
方法生成的一个匿名函数。这样会导致无法取消绑定
element.addEventListener('click', o.m.bind(o));
element.removeEventListener('click', o.m.bind(o));
正确的方法是写法:
var listener = o.m.bind(o);
element.addEventListener('click', listener);
// ...
element.removeEventListener('click', listener);
区别:
例1
var a ={
name : "Cherry",
fn : function (a,b) {
console.log( a + b)
}
}
var b = a.fn;
b.apply(a,[1,2]) // 3
例2
var a ={
name : "Cherry",
fn : function (a,b) {
console.log( a + b)
}
}
var b = a.fn;
b.call(a,1,2) // 3
apply 和 call 的区别是 call 方法接受的是若干个参数列表,而 apply 接收的是一个包含多个参数的数组。
例3
var a ={
name : "Cherry",
fn : function (a,b) {
console.log( a + b)
}
}
var b = a.fn;
b.bind(a,1,2)
bind()方法创建一个新的函数, 当被调用时,将其this关键字设置为提供的值,在调用新函数时,在任何提供之前提供一个给定的参数序列。MDN
因此我们需手动调用
var a ={
name : "Cherry",
fn : function (a,b) {
console.log( a + b)
}
}
var b = a.fn;
b.bind(a,1,2)() // 3
总结
1. call 、 apply 、bind 均能改变this 指向
2. bind 每次执行产生一个新函数,call、apply 不会
3. call ,bind 接收多个参数绑定到函数,参数单一传入,apply 接收方式为数组
参考:
https://juejin.im/post/6844903496253177863
https://javascript.ruanyifeng.com/oop/this.html#toc7