授业至今,技艺日有进益,许是看的多了,先时似是而非的东西,今时也多有顿悟,call和apply学的早,用得少,同是作用于this,bind就用的多,但这个函数扩展方法是ECMAscript5里的,IE[6-8]不支持,移动端可以放心用,这里说与某家主公知道。
this之于call、apply和bind,五花肉之于东坡肉、红烧肉和回锅肉,反复思量,深感经世以来比喻之周全者未有过于此者。因其关键,蒙师授业之初费了大周折讲它,终了一句话“.前头谁调用,this就是谁,没有.前头,就是window”醍醐灌顶。
js中this总是指向一个对象,情况有4种
1、作为对象的方法调用
var persion = {
name : "haha",
getName : function(){
console.log(this === persion);//true
console.log(this.name);//"haha"
}
};
persion.getName();
2、作为普通函数调用
此时this总是指向全局对象window
window.name = "global";
var getName = function(){
return this.name;
}
console.log(getName());//"global"
或者
window.name = "global";
var persion = {
name : "haha",
getName : function(){
return this.name;
}
}
console.log(persion.getName());//"haha"
var getName = persion.getName;
console.log(getName());//"global"
DOM节点的事件函数内部,有个局部的callback方法,callback被当做普通函数调用时,callback内部的this指向了window,但是我们想让它指向该DOM节点
document.getElementById("btn").onclick = function(){
console.log(this.id)//"btn"
var callback = function(){
console.log(this) //window
console.log(this.id) //undefined
}
callback();
}
常用的解决方案
document.getElementById("btn").onclick = function(){
console.log(this.id)//"btn"
var that = this;
var callback = function(){
console.log(that.id)//"btn"
}
callback();
}
在ECMAScript5的strict严格模式下
document.getElementById("btn").onclick = function(){
"use strict"
console.log(this.id)//"btn"
var callback = function(){
console.log(this) //undefined
console.log(this.id) //undefined
}
callback();
}
strict这种模式多余的很
3、构造器调用
js函数大多数都可以当做构造器使用,当用new 运算符调用函数时,该函数总会返回一个对象,通常情况下,构造器里的this就指向返回的这个对象
var Persion = function(){
this.name = "haha";
}
var persion1 = new Persion();
console.log(persion1.name);//"haha"
用new调用构造器时,如果构造器显式地返回了一个object类型的对象,那么运行的结果就是返回的这个对象,不是之前期待的那个this
var Persion = function(){
this.name = "haha";
return {
name : "hehe"
}
}
var persion1 = new Persion();
console.log(persion1.name);//"hehe"
构造器不显式返回数据或者返回非对象类型的数据,不会造成上述问题
var Persion = function(){
this.name = "haha";
return "hehe";
}
var persion1 = new Persion();
console.log(persion1.name);//"haha"
4、call和apply调用
call和apply可以动态地改变传入函数的this
var persion1 = {
name : "haha",
getName : function(){
console.log(this.name + " from persion1");
}
};
var persion2 = {
name : "hehe",
getName : function(){
console.log(this.name + " from persion2");
}
};
console.log(persion1.getName.call(persion2));// "hehe from persion2"
call和apply
call和apply都是改变this指向,区别在于参数传入的形式不同
var func = function(a,b,c){
console.log([a,b,c]);//[1,2,3]
}
func.call(null,1,2,3);
func.apply(null,[1,2,3]);
call和apply第一个参数传入的都是函数体内的this指向,不同在于call第二参数往后依次传入与函数体形参相对应的实参,apply第二个参数是一个实参数组,数组中的元素同样与函数体的形参相对应。
孰用孰不用,有一种说法apply比call的使用效率更高,因为在js的参数在内部就是用一个数组来表示的,用arguments可以访问到。
使用call和apply的时候传入的第一个参数是null,函数执行的时候this会指向默认的宿主对象,也就是window。
var func = function(a,b,c){
console.log(this === window);//true
}
func.call(null,1,2,3);
严格模式下情况就不一样了
var func = function(a,b,c){
console.log(this === null);//true
}
func.call(null,1,2,3);
它们的用途
1、改变this指向
var persion1 = {
name : "haha",
getName : function(){
console.log(this.name + " from persion1");
}
};
var persion2 = {
name : "hehe",
getName : function(){
console.log(this.name + " from persion2");
}
};
console.log(persion1.getName.call(persion2));// "hehe from persion2"
一样的代码再来一遍
2、模拟bind方法
bind方法低级浏览器里没有,但可以用apply来封装
Function.prototype.bind = function(context){
var self = this;
return function(){
return self.apply(context,arguments);
}
}
var obj = {
name:"haha"
}
var getName = function(){
console.log(this.name);//"haha"
}.bind(obj);
3、借用其他对象方法
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('hehe');
console.log(b.getName());//"hehe"
上面是的场景是“借用构造函数”,通过这种技术,可以实现出类似继承的效果,此外还有一种借用场景
(function(){
Array.prototype.push.apply(arguments,3);
console.log(arguments);//[1,2,3]
})(1,2)
函数的参数列表arguments是一个类数组
(function(){
console.log(typeof arguments);//object
Array.prototype.push.call(arguments,3);
console.log(arguments);//[1,2,3]
})(1,2)
本身不具备push方法,可以使用call或apply借用Array.prototype对象的方法,前提是借用方法的对象要可以存取属性、length属性可读写
《JavaScript设计模式与开发实践》是本好书