JavaScript设计模式与开发实践(2)

写在前面:由于在前面更新的红宝书中发现红宝书的在很多地方的知识点只是一笔带过,并没有深入的去讲,所以个人的主要更新多拉了一个进程去更新这个设计模式与开发实践,在学习设计模式之前,会学习一些基础的知识点,个人觉得这些基础的知识点讲的很不错,所以在这里会从基础开始记录,更新的顺序和书内篇章会有偏差,但章节没有错乱。

2,this、call 和 apply

在 JavaScript 编程中,this 关键字总是让初学者感到迷惑,Function.prototype.call 和Function.prototype.apply 这两个方法也有着广泛的运用。我们有必要在学习设计模式之前先理解这几个概念。

2.1 this

JavaScript 的 this 总是指向一个对象,而具体指向哪个对象是在运行时基于函数的执行环境动态绑定的,而非函数被声明时的环境。 所以在全局作用域调用函数的时候,函数的this应当属于window。

2.1.1 this的指向

具体到实际应用中,this 的指向大致可以分为以下 4 种。

  1. 作为对象的方法调用。
  2. 作为普通函数调用。
  3. 构造器调用。
  4. Function.prototype.call 或 Function.prototype.apply 调用。

1. 作为对象的方法调用
当函数作为对象的方法被调用时,this 指向该对象。

var obj = {
	 a: 1,
	 getA: function(){
		 alert ( this === obj ); // 输出:true
		 alert ( this.a ); // 输出: 1
 	}
};
obj.getA(); 

2. 作为普通函数调用
当函数不作为对象的属性被调用时,也就是我们常说的普通函数方式,此时的 this 总是指向全局对象。在浏览器的 JavaScript 里,这个全局对象是 window 对象。

window.name = 'globalName';
var getName = function(){
 	return this.name;
};
console.log( getName() ); // 输出:globalName
//或者:
window.name = 'globalName';
var myObject = {
	 name: 'sven',
	 getName: function(){
	 	return this.name;
	 }
};
var getName = myObject.getName;
console.log( getName() ); // globalName

3. 构造器调用
当用 new 运算符调用函数时,该函数总会返回一个对象,通常情况下,构造器里的 this 就指向返回的这个对象,见如下代码:

var MyClass = function(){
 	this.name = 'sven';
};
var obj = new MyClass();
alert ( obj.name ); // 输出:sven 

但用 new 调用构造器时,还要注意一个问题,如果构造器显式地返回了一个 object 类型的对象,那么此次运算结果最终会返回这个对象,而不是我们之前期待的 this:

var MyClass = function(){
 this.name = 'sven';
 return { // 显式地返回一个对象
 	name: 'anne'
 }
};
var obj = new MyClass();
alert ( obj.name ); // 输出:anne

如果构造器不显式地返回任何数据,或者是返回一个非对象类型的数据,就不会造成上述问题:

var MyClass = function(){
	 this.name = 'sven'
	 return 'anne'; // 返回 string 类型
};
var obj = new MyClass();
alert ( obj.name ); // 输出:sven 

4. Function.prototype.call 或 Function.prototype.apply 调用
跟普通的函数调用相比,用 Function.prototype.call 或Function.prototype.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 

原因是第一个是对象调用,所以this指向的是obj,getName2接收的是obj.getName的地址值,且调用的时候是函数调用,所以this指向window。

2.2 call 和 apply

在 JavaScript 版本的设计模式中,这两个方法的应用非常广泛,能熟练运用这两个方法,是我们真正成为一名 JavaScript 程序员的重要一步。

2.2.1 call和apply的区别

Function.prototype.call 和 Function.prototype.apply 都是非常常用的方法。它们的作用一模一样,区别仅在于传入参数形式的不同。
apply 接受两个参数,第一个参数指定了函数体内 this 对象的指向,第二个参数为一个带下标的集合,这个集合可以为数组,也可以为类数组,apply 方法把这个集合中的元素作为参数传递给被调用的函数。
call 传入的参数数量不固定,跟 apply 相同的是,第一个参数也是代表函数体内的 this 指向,从第二个参数开始往后,每个参数被依次传入函数。

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 );

call 是包装在 apply 上面的一颗语法糖,如果我们明确地知道函数接受多少个参数,而且想一目了然地表达形参和实参的对应关系,那么也可以用 call 来传送参数。
当使用 call 或者 apply 的时候,如果我们传入的第一个参数为 null,函数体内的 this 会指向默认的宿主对象,在浏览器中则是 window。(严格模式下为this为null)。

2.2.2 call和apply的用途

1. 改变 this 指向
前面已经说的很详细了,这里略过。
2. Function.prototype.bind
当浏览器不支持Function.prototype.bind的时候,可以用call或者apply实现。

Function.prototype.bind = function( context ){
 var self = this; // 保存原函数
 return function(){ // 返回一个新的函数
 	return self.apply( context, arguments ); // 执行新的函数的时候,会把之前传入的 context当作新函数体内的 this
 }
};
var obj = {
 name: 'sven'
};
var func = function(){
 	alert ( this.name ); // 输出:sven
}.bind( obj);
func(); 

这是一个不含参数的情况,下面是包含参数的情况

Function.prototype.bind = function(){
 var self = this, // 保存原函数
 context = [].shift.call( arguments ), // 需要绑定的 this 上下文
 args = [].slice.call( arguments ); // 剩余的参数转成数组
 return function(){ // 返回一个新的函数
 	return self.apply( context, [].concat.call( args,[].slice.call( arguments ) ) );
 // 执行新的函数的时候,会把之前传入的 context 当作新函数体内的 this
 // 并且组合两次分别传入的参数,作为新函数的参数
 }
 };
var obj = {
 	name: 'sven'
};
var func = function( a, b, c, d ){
 	alert ( this.name ); // 输出:sven
 	alert ( [ a, b, c, d ] ) // 输出:[ 1, 2, 3, 4 ]
}.bind( obj, 1, 2 );
func( 3, 4 ); 

3. 借用其他对象的方法
我们知道,杜鹃既不会筑巢,也不会孵雏,而是把自己的蛋寄托给云雀等其他鸟类,让它们代为孵化和养育。同样,在 JavaScript 中也存在类似的借用现象。
借用方法的第一种场景是“借用构造函数”,通过这种技术,可以实现一些类似继承的效果:

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( 'sven' );
console.log( b.getName() ); // 输出: 'sven'

简单的解释一下上面的代码,由于用了new操作符修饰了function B,所以B是构造函数,所以B内会创建一个对象,并把this赋值给该对象,执行B内的代码,B内的代码有A.apply( this, arguments ),此时this指向的是新创建的对象,也可以理解为接收的b,在执行A的时候,A内的this是b,所以name也就是‘sven’,所以 b.getName()输出‘sven’。
借用方法的第二种运用场景跟我们的关系更加密切。
函数的参数列表 arguments 是一个类数组对象,虽然它也有“下标”,但它并非真正的数组,所以也不能像数组一样,进行排序操作或者往集合里添加一个新的元素。这种情况下,我们常常会借用 Array.prototype 对象上的方法。比如想往 arguments 中添加一个新的元素,通常会借用
Array.prototype.push:

(function(){
 Array.prototype.push.call( arguments, 3 );
 console.log ( arguments ); // 输出[1,2,3]
})( 1, 2 );

Array.prototype.push 实际上是一个属性复制的过程,把参数按照
下标依次添加到被 push 的对象上面,顺便修改了这个对象的 length 属性。至于被修改的对象是谁,到底是数组还是类数组对象,这一点并不重要。

function ArrayPush() {
	 var n = TO_UINT32( this.length ); // 被 push 的对象的 length
	 var m = %_ArgumentsLength(); // push 的参数个数
	 for (var i = 0; i < m; i++) {
	 	this[ i + n ] = %_Arguments( i ); // 复制元素 (1)
 	}
	this.length = n + m; // 修正 length 属性的值 (2)
 	return this.length;
}; 

由此可以推断,我们可以把“任意”对象传入 Array.prototype.push:

var a = {};
Array.prototype.push.call( a, 'first' );
alert ( a.length ); // 输出:1
alert ( a[ 0 ] ); // first

前面我们之所以把“任意”两字加了双引号,是因为可以借用Array.prototype.push 方法的对象还要满足以下两个条件,从 ArrayPush 函数的(1)处和(2)处也可以猜到,这个对象至少还要满足:
对象本身要可以存取属性;对象的 length 属性可读写。
如:

var a = 1;
Array.prototype.push.call( a, 'first' );
alert ( a.length ); // 输出:undefined
alert ( a[ 0 ] ); // 输出:undefined 

var func = function(){};
Array.prototype.push.call( func, 'first' );
alert ( func.length );
// 报错:cannot assign to read only property ‘length’ of function(){}
  • 37
    点赞
  • 39
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值