this的指向
在js中,this总是回指向一个对象,需要注意的是,具体指向哪个对象实在运行时基于函数的执行环境动态绑定的,并非函数声明时的环境。又或者说:this 永远指向最后调用它的那个对象。
this的指向可以大致分为四种:
- 作为对象的方法使用
- 作为普通函数使用
- 构造函数调用
- call,apply方法调用
作为对象的方法调用
在 JavaScript 中, 函数就是对象。
当函数作为对象的方法被调用时,this指向该对象。
var name = "globalName";
var obj = {
name: 'objName',
getName: function () {
console.log(this.name);
}
}
obj.getName(); // objName
此时this指向最后调用他的对象,也就是obj。
作为普通函数调用
当函数不作为对象的属性调用时,就是一个普通函数,这样的情况在 JavaScript 的在浏览器中的非严格模式默认是属于全局对象 window 的,在严格模式,就是 undefined。
var name = "globalName";
var obj = {
name: 'objName',
getName: function () {
console.log(this.name);
}
}
var getName = obj.getName;
getName(); // globalName
此时虽然getName()是在对象obj中声明的方法,但是在实际执行该方法的时候,其调用对象已经不是obj,而是全局对象 window,那么这时getName()就是作为一个普通函数调用。
构造器调用
JavaScript中没有类,但是可以通过构造器创建对象,如果函数调用前使用了new关键词,则是调用了构造函数,返回的总是一个对象,这使得构造器看起来像一个类。
// 构造器
function constructor (name) {
this.name = name;
}
// new object
var obj = new constructor('objName');
obj.name; // objName
但是使用构造器创建对象时,需要注意一个问题,如果构造器显示返回一个object类型的对象,那么此次new的结果会返回该对象。
// 构造器
function constructor (name) {
this.name = name;
return {
name: 'anotherName'
}
}
// new object
var obj = new constructor('objName');
obj.name; // anotherName
call,apply方法调用
与前面的几种方式相比,使用call或者apply可以动态地修改传入函数的this:
var obj1 = {
name: 'obj1Name',
getName: function () {
console.log(this.name);
}
}
var obj2 = {
name: 'obj2Name'
}
obj1.getName(); // obj1Name
obj1.getName.call(obj2); // obj2Name
obj1.getName.apply(obj2); // obj2Name
由于call或者apply方法改变了getName方法中this的指向,即将对象obj1的方法“借”给了对象obj2去执行。
call 和 apply
Function.prototype.call 和 Function.prototype.apply 在实际开发中,特别是在函数式风格的代码编写中,非常有用,去看大神的代码,也会经常看到,应用非常广泛。
call 和 apply 的区别
Function.prototype.call 和 Function.prototype.apply 这两个方法的作用一模一样,区别在于传参形式不同。
apply 一共只有两个参数:
第一个参数 是函数体中this指向的对象,
第二个参数 为一个带下标的集合,可以是数组或类数组,
apply 会把这个集合中的元素作为参数一一传递给被调用的函数。
var func = function (arg1, arg2) {
console.log(arg1, arg2)
};
func.apply(obj, ['第一个参数', '第二个参数']);
语法:func.apply(obj, [arg1, arg2, ...])
call 则参数数量不固定,但是跟 apply 一样
第一个参数 是函数体中this指向的对象
第二个参数开始,每个参数都会被一一传递给被调用的函数。
var func = function (arg1, arg2) {
console.log(arg1, arg2)
};
func.call(obj, '第一个参数', '第二个参数');
语法:func.apply(obj, arg1, arg2, ...)
总结就是对参数是否用集合包裹起来的区别。但是apply的效率会比call高。
实际上上面的代码块执行时是报错的,原因是obj并没有定义,当我们没有目标对象传的时候,第一个参数可以传null,函数体内的this会指向默认的宿主对象,在浏览器中则为window
func.call(null, '第一个参数', '第二个参数'); // 等同于 window.func('第一个参数', '第二个参数')
call 和 apply的用途
改变this的指向
上面我们已经说过call与apply可以用来改变函数体内部this的指向对象,但是在实际开发中经常会遇到this指向被不经意改变的场景,那么这里再举个🌰 例子加深一下印象。
比如有个div节点,节点的onclick事件中的this本来是指向这个div的:
<div id="id1">JS</div>
...
document.getElementById('id1').onclick = function() {
console.log(this.id); // id1
}
但如果变成这,在函数内部增加一个函数func:
<div id="id1">JS</div>
...
document.getElementById('id1').onclick = function() {
console.log(this.id); // id1
var func = function () {
console.log(this.id); // undefined
}
func();
}
在onclick事件内部调用func函数时,func的this指向了window,而不是div,这时候就可以用call,或者apply来修正this的指向。
<div id="id1">JS</div>
...
document.getElementById('id1').onclick = function() {
console.log(this.id); // id1
var func = function () {
console.log(this.id); // id1
}
func.apply(this);
}
另外,大部分浏览器都实现了内置的Function.prototype.bind,也是用来指定函数内部的this指向,用法与call类似,只不过bind 是创建一个新的函数,我们必须要手动去调用:
document.getElementById('id1').onclick = function() { console.log(this.id); // id1 var func = function () { console.log(this.id); // id1 } func.bind(this)(); }
借用其他对象的方法
常见的有“借用构造函数”,这样可以实现一些类似继承的效果:
var A = function (name) {
this.name = name;
};
var B = function () {
A.apply(this, arguments)
};
B.prototype.getName = function () {
return this.name;
};
var b = new B('bClassName');
b.getName(); // bClassName
这里的B实际上就是借用了A构造函数,也就是构造函数式继承。
另外,下面这种运用场景会更密切一点。
函数的参数arguments是一个类数组对象,虽然有“下标”,但不是真正的数组,自然不能使用obj.push(), obj.slice()等属于Array的方法。
那这种情况下,我们可以使用Array.prototype对象上的方法来处理这样的类数组对象。
例如:document.getElementsByClassName() 得到的就是一个HTMLCollection对象,直接调用forEach的方式是不行的,这时候就可以找Array借一个forEach使用了。
再如
var a = {}
Array.prototype.push.call(a, 'first')
console.log(a.length); // 1
console.log(a[0]); // first
console.log(a); // {0: "first", length: 1}
这段代码在绝大部分浏览器都能顺利执行,在低版本的IE则需要显示给a对象设置length属性
类似上面的“借用”的方式还有很多,这里就不赘述了。