JavaScript设计模式笔记-this,call,apply
2.1 this
JavaScript中的this总是指向一个对象,而具体指向哪个对象是在运行时基于函数的执行环境动态绑定的.
2.1.1 this的指向
this的指向大致分为以下4种
- 作为对象的方法调用
- 作为普通函数调用
- 构造器调用
- Function.prototype.call或者Function.prototype.apply调用
1.作为对象的方法调用当作为对象的方法调用,this指向该对象
var obj={
a:1,
getA:function(){
console.log(this===obj);//true
console.log(this.a);//1
}
};
obj.getA()
2.作为普通函数调用当函数不作为对象的属性调用,也就是我们常说的用普通函数方法,此时的this总是指向全局对象,浏览器中是window
window.name='globalname';
var getName=function(){
return this.name
}
console.log(getName());//globalname
但是在事件类部函数里定义一个函数并且调用,因为普通函数方法this是永远指向window的,比如下例的问题
<div id='div1'></div>
$('div1').onclick=function(){
console.log(this.id);//div1
var callback=function(){
console.log(this.id)
}
callback();//window
}
那么可以一个变量保存一下div1中的变量
$('div1').onclick=function(){
console.log(this.id);//div1
var that=this
var callback=function(){
console.log(that.id)
}
callback();//div1
}
ES5严格模式中,this指向的是undefined
3.构造器调用当用new运算符调用函数时,函数总会返回一个对象,通常情况下,构造器里的this就指向返回的这个对象
var myClass=function(){
this.name='sven'
}
var obj=new Myclass()
console.log(obj.name);//sven
但是如果显式的返回了一个object那么就会返回这个object而不是我们之前期待的this,返回一个非对象类型的数据是不会影响的
var myClass=function(){
this.name='sven'
return{
name:'anne'
}
}
var obj=new Myclass()
console.log(obj.name);//anne
----
var myClass=function(){
this.name='sven'
return 'anne'
}
var obj=new Myclass()
console.log(obj.name);//sven
4.Function.prototype.call或者Function.prototype.apply调用call和apply可以动态的改变this指向.
var obj1={
name:'sven',
getName:function(){
return this.name
}
};
var obj2={
name:'anne'
}
console.log(obj1.getName());//sven
console.log(obj1.getName.call(obj2));//anne
2.1.2丢失的this
//这是一个经常遇到的问题,我们先看下面的代码:
var obj = {
myName: 'sven',
getName: function(){
return this.myName;
}
};
console.log( obj.getName() ); // 输出:'sven'
var getName2 = obj.getName;
console.log( getName2() ); // 输出:undefined
注意getName2并不是执行后赋值,而是把这个函数赋值给他,执行的时候参考第二条,普通函数的this指向.所以执行后的结果是undefined下例这个例子可以更加清楚的说明问题所在
var getId=document.getElementById;
getId('div1');//error
默认的document.getElementById方法运行的时候this指向的是document这个object,但是这里赋值后再运行相当于window.getId()所以会报错.不过可以利用apply来修复这个问题
document.getElementById=function(){
return function(){
return document.getElementById.apply(document,arguments)
}
}(document.getElementById)
var getId = document.getElementById;
var div = getId( 'div1' );
alert (div.id); // 输出: div1
2.2 call与apply
2.2.1 call与apply区别
简单来说,call和apply仅仅是传递的调入参数不同.
//apply 方法把这个集合中的元素作为参数传递给被调用的函数:
var func = function( a, b, c ){
alert ( [ a, b, c ] ); // 输出 [ 1, 2, 3 ]
};
func.apply( null, [ 1, 2, 3 ] );
//从第二个参数开始往后,每个参数被依次传入函数:
var func = function( a, b, c ){
alert ( [ a, b, c ] ); // 输出 [ 1, 2, 3 ]
};
func.call( null, 1, 2, 3 );
apply会扁平化参数罢了在浏览器中如果我们第一个参数传递的为null,那么函数体内的this会指向默认的宿主对象,在浏览器中是window,但是在严格模式下this还是指向null
//当使用call 或者apply 的时候,如果我们传入的第一个参数为null,函数体内的this 会指向默认的宿主对象,在浏览器中则是window:
var func = function( a, b, c ){
alert ( this === window ); // 输出true
};
func.apply( null, [ 1, 2, 3 ] );
//但如果是在严格模式下,函数体内的this 还是为null:
var func = function( a, b, c ){
"use strict";
alert ( this === null ); // 输出true
}
func.apply( null, [ 1, 2, 3 ] );
平时传递null的条件一般都是借用一些方法.
Math.max.apply(null,[1,2,3]);//3
2.2.2 call与apply用途
1.改变this的指向
var obj1={
name:'sven'
};
var obj2={
name:'anne'
}
window.name='window'
var getName=function(){
console.log(this.name)
}
getName.apply(obj1);//sven
getName.apply(obj2);//anne
getName.apply(window);//window
2. Function.prototype.bind
虽然ES5添加了bind,但是ES5以下的版本并没有这个函数,那么我们可以手动写一个
Function.prototype.bind=function(context){
var self=this;//保存原函数
return function(){
return self.apply(context,arguments)
}
}
var obj1={
name:'sven'
}
var func=function(){
console.log(this.name);//sven
}.bind(obj1);
func();
分析下这段代码.首先调用Bind会触发第一个function(context)把context传递进去 上例的context是obj1,执行后返回内部function,那么问题来了 如果不保存self=this 直接写 this.apply这个this是什么? 参考this第二条 如果不保存那么this指向的就是window.保存后self指向的就是调用者也就是func函数,这段代码翻译过来就是func.apply(context,arguments)因为arguments是类数组,没有那些方法所以需要利用apply和call上例是一个简化版的,下例基本上就符合bind函数实现了.
Function.prototype.bind=function(context){
var self=this,//保存原函数
context=[].shift.call(arguments),//需要绑定this的上下文,shift会操作原数组
args=[].slice.call(arguments),//原函数的参数,奇淫技巧,利用数组的特性借用slice把arguments转为数组
return function(){
return self.apply(context,[].concat.call(args,[].slice.call(arguments)))
//组合两次传递的参数
}
}
var func=function(a,b,c,d){
console.log(this.name);//sven
console.log([a,b,c,d])//[1,2,3,4]
}.bind(obj1,1,2);
func(3,4);
首先在调用bind函数时,会先保存原函数的参数和上下文也就是obj1和1,2然后调用func函数的时候,会把3,4与1,2组合起来也就是这条语句self.apply(context,[].concat.call(args,[].slice.call(arguments)))
3. 借用其他对象的方法
最常见的例子就是arguments,上述例子已经可以说明了.JavaScript设计模式与开发实践中一书在这里说到了Array.prototype.push 这里不做笔记.因为要表达的意思是一样的.