最近看了一本书叫《JavaScript设计模式与开发实践》, 目前暂时只看完了第一部分,讲述的是学习设计模式前需要掌握的比较熟练的一些概念, 在这里简单做一些总结。
第一部分 基础知识
第一章 面向对象的基础知识
JavaScript作为一门动态类型语言(定义变量时不需要指定特定类型,在运行时才能确定),在编译时没有类型检查的过程,既没有检查创建的对象类型,又没有检查函数执行时传递的参数类型,因此它自然而然就有多态的效果。多态的思想实际上是把做什么和谁去做分离开来。
多态的含义: 同一个操作作用域不同的对象上面,可以产生不同的解释和不同的执行效果。换句话说,给不同的对象发送同一个消息的时候,这些对象可以根据这个消息分贝给出不同的反馈,举例如下:
var maekSound = function(animal) {
if (animal instanceof Duck) {
console.log('嘎嘎嘎');
} else if (animal instanceof Chicken) {
console.log('咯咯咯');
}
};
var Duck = function() {};
var Chicken = function() {};
makeSound( new Duck() ); // 嘎嘎嘎
maekSound( new Chicken() ); // 咯咯咯
多态最根本的作用就是通过把过程化的条件分支语句转换为对象的多态性,从而消除这些条件分支语句
第二章 原型模式
原型模式是创建对象的一种模式,实现的方式是找到一个对象,然后通过克隆的方式创建一个一模一样的对象。
原型模式的实现关键,是语言本身是否提供了clone方法. ECMAScript5提供了Object.create方法,可以用来克隆对象。如下面代码所示:
var Plane = function() {
this.blood = 100;
this.attackLevel = 1;
this.defenseLevel = 1;
};
var plane = new Plane();
plane.blood = 500;
plane.attackLevel = 10;
plane.defenseLevel = 7;
var clonePlane = Object.create( plane );
console.log(clonePlane); // 输出Plane{__proto__:{attackLevel:10, blood:500, defenseLevel: 7}}
通过上面代码可以看到Object.create的方法其实跟下面是一样的
function create(obj) {
var F = function() {};
F.prototype = obj;
return new F();
}
也就是说,通过Object.create克隆出来的对象所具备的复制出来的属性,并不是保存在对象本身,而是保存在对象(clonePlane)的__proto__指向的对象上
2.1 原型编程泛型
原型编程泛型至少包括以下基本规则:
- 所有的数据都是对象
- 要得到一个对象,不是通过实例化类,而是找到一个对象作为原型并且克隆它
- 对象会记住它的原型
- 如果一个对象无法响应某个请求,它会把这个请求委托给它自己的原型
2.2 JavaScript的根对象
事实上,JavaScript中的根对象是Object.prototype对象。Object.prototype对象是一个空的对象。我们在JavaScript中遇到的每个对象实际上都是从Object.prototype对象克隆出来的,Object.prototype对象就是它们的原型。比如下面的obj1对象和obj2对象:
var obj1 = new Object();
var obj2 = {};
// Object.getPrototypeOf可以查看某个对象的原型
console.log(Object.getPrototypeOf(obj1)===Object.prototype) // true
console.log(Object.getPrototypeOf(obj2)===Object.prototype) // true
2.3 理解new操作符执行了哪些过程
下面使用一个objectFactory来模拟new操作符执行的操作
var objectFactory = function() {
var obj = new Object();
Constructor = [].shift.call(arguments);
obj.__proto__ = Constructor.prototype;
var ret = Constructor.apply(obj, arguments);
return typeof ret === 'object' ? ret : obj;
}
总结一下就是,通过new Object() 从Object.prototype上克隆一个空的对象,然后继承构造函数的原型对象里面的内容,赋给克隆出的对象的__proto__属性上,然后将构造函数执行一遍并且将创建出来的对象传递进去作为上下文对象,最后查看构造函数中是否返回对象,如果有的话将该对象返回,如果没有则将刚刚new操作得到的对象返回
2.4 如果一个对象无法响应某个请求,它会把这个请求委托给它的构造器的原型
这条规则是原型继承的精髓所在:当一个对象无法响应某个请求时,它会顺着原型链把请求传递下去,知道遇到一个可以处理该请求的对象为止,我们看看下面的例子:
var A = function() {};
A.prototype = {name: 'sevn'};
var B = function() {};
B.prototype = new A()l
var b = new B();
console.log(b.name); // 输出sven
看看这段代码执行的过程中引擎做了什么事情:
(1)首先,尝试遍历对象b中的所有属性,但没有找到name这个属性
(2)查找name属性的请求被委托给对象b的构造器的原型,它被b.__proto__记录着并且指向了B.prototype, 而B.prototype被设置为一个通过new A()创建出来的实例对象
(3)在这个对象中仍然没有找到name属性,于是请求被继续委托给这个对象的构造器的原型A.prototype.
(4)在A.prototype中找到了name属性,并返回它的值。
假设,如果A.prototype中仍然没有找到,就会到A.prototype.__proto__,也就是Object.prototype中查找,如果在Object.prototype也没有找到,此时不会继续往下找了(因为Object.prototype.__proto__指向的是null), 此时最终得到的结果是undefined
第三章 this
3.1 构造器调用
使用new调用构造器时,需要注意的一个问题就是,如果构造器显式地返回了一个object类型的对象,那么此次运算结果最终会返回这个对象,而不是我们之前期待的this:
var MyClass = funciton() {
this.name = 'sven';
return { // 显式地返回一个对象
name: 'anne'
}
};
var obj = new MyClass();
console.log(obj.name); // 输出anne
如果构造器不显式地返回任何数据,或者是返回一个非对象类型的数据,就不会造成上面问题:
var MyClass = function() {
this.name = 'sven';
return 'anne'; // 返回string类型
};
var obj = new MyClass();
console.log(obj.name); // 输出sven
3.2 call和apply
3.2.1 this指向null
当我们使用call或者apply的时候,如果我们传入的第一个参数为null,函数体内的this会指向默认的宿主对象,在浏览器中则是windowl
3.2.2 实际应用
3.2.2.1 参数列表arguments
函数的参数列表arguments是一个类数组对象,虽然它也有“下标“,但是它并非真正的数组,所以也不能像数组一样,进行排序操作或者往集合里添加一个新的元素。这种情况下我们常常会借用Array.prototype对象上的方法。比如我想往arguments中添加一个新的元素,通常会借用Array.prototype.push:
(function() {
Array.prototype.push.call(arguments, 3);
console.log(arguments); // 输出[1,2,3]
})(1, 2)
第四章 闭包
4.1 实际用途
4.1.1 封装变量
闭包可以帮助把一些不需要暴露在全局的变量封装成“私有变量”。假设有一个计算乘积的简单函数:
var mult = function(){
var a = 1;
for ( var i = 0, l = arguments.length; i < l; i++) {
a = a * arguments[i];
}
return a;
}
mult 函数接受一些 number 类型的参数,并返回这些参数的乘积。现在我们觉得对于那些相同 的参数来说,每次都进行计算是一种浪费,我们可以加入缓存机制来提高这个函数的性能:
var mult = (function(){
var cache = {};
return function(){
var args = Array.prototype.join.call( arguments, ',' );
if ( args in cache ){
return cache[ args ];
}
var a = 1;
for ( var i = 0, l = arguments.length; i < l; i++ ){
a = a * arguments[i];
}
return cache[ args ] = a;
}
})();
4.1.2 延续局部变量的寿命
img 对象经常用于进行数据上报,如下所示:
var report = function( src ){
var img = new Image();
img.src = src;
};
report( 'http://xxx.com/getUserInfo' );
但是通过查询后台的记录我们得知,因为一些低版本浏览器的实现存在 bug,在这些浏览器 下使用 report 函数进行数据上报会丢失 30%左右的数据,也就是说,report 函数并不是每一次 都成功发起了 HTTP 请求。丢失数据的原因是 img 是 report 函数中的局部变量,当 report 函数的 调用结束后,img 局部变量随即被销毁,而此时或许还没来得及发出 HTTP 请求,所以此次请求 就会丢失掉。
现在我们把 img 变量用闭包封闭起来,便能解决请求丢失的问题:
var report = (function(){
var imgs = [];
return function( src ){
var img = new Image();
imgs.push( img );
img.src = src;
}
})();
4.2 高阶函数
4.2.1 什么是高阶函数
高阶函数是指至少满足下列条件之一的函数。
- 函数可以作为参数被传递;
- 函数可以作为返回值输出。
4.2.2 应用场景
4.2.2.1 函数作为参数传递
(1) ajax请求中的回调函数
(2) Array.prototype.sort方法
4.2.2.2 函数作为返回值输出
(1) 判断数据的类型
我们来看看这个例子,判断一个数据是否是数组,在以往的实现中,可以基于鸭子类型的概 念来判断,比如判断这个数据有没有 length 属性,有没有 sort 方法或者 slice 方法等。但更好 的方式是用 Object.prototype.toString 来计算。Object.prototype.toString.call( obj )返回一个 字符串,比如 Object.prototype.toString.call( [1,2,3] )总是返回”[object Array]”,而 Object.prototype.toString.call( “str”)总是返回”[object String]”。所以我们可以编写一系列的 isType 函数。代码如下:
var isString = function( obj ){
return Object.prototype.toString.call( obj ) === '[object String]';
};
var isArray = function( obj ){
return Object.prototype.toString.call( obj ) === '[object Array]';
};
var isNumber = function( obj ){
return Object.prototype.toString.call( obj ) === '[object Number]';
};
我们发现,这些函数的大部分实现都是相同的,不同的只是 Object.prototype.toString. call( obj )返回的字符串。为了避免多余的代码,我们尝试把这些字符串作为参数提前值入 isType 函数。代码如下:
var isType = function( type ){
return function( obj ){
return Object.prototype.toString.call( obj ) === '[object '+ type +']';
}
};
var isString = isType( 'String' );
var isArray = isType( 'Array' );
var isNumber = isType( 'Number' );
console.log( isArray( [ 1, 2, 3 ] ) ); // true
我们还可以用循环语句,来批量注册这些 isType 函数:
var Type = {};
for ( var i = 0, type; type = [ 'String', 'Array', 'Number' ][ i++ ]; ){
(function( type ){
Type[ 'is' + type ] = function( obj ){
return Object.prototype.toString.call( obj ) === '[object '+ type +']';
};
})( type )
Type.isArray( [] );
Type.isString( "str" );
// 输出:true
// 输出:true