Chapter1 - 面向对象的Javascript
Javascript通过原型委托的方式实现继承。
动态类型语言的变量类型要到程序运行的时候,待变量被赋予某个值之后,才会具有某种类型。
鸭子类型(duck typing):“如果它走起路来像鸭子,叫起来也是鸭子,那么它就是鸭子。”
varduck={duckSinging: function(){console.log('嘎嘎嘎');}};varchicken={duckSinging: function(){console.log('嘎嘎嘎');}};varchoir=[];// 合唱团varjoinChoir=function(animal){if(animal&&typeofanimal.duckSinging==='function'){choir.push(animal);console.log('恭喜加入合唱团');console.log('合唱团已有成员数量:'+choir.length);}};joinChoir(duck);// 恭喜加入合唱团joinChoir(chicken);// 恭喜加入合唱团多态: 同一操作作用于不同的对象上面,可以产生不同的解释和不同的执行结果。多态的思想实际上是把“做什么”和“谁去做”分离开来。要实现这一点,归根结底要先消除类型之间的耦合关系。
“JavaScript对象的多态性是与生俱来的: “JavaScript的变量类型在运行期是可变的。一个JavaScript对象,既可以表示Duck类型的对象,又可以表示Chicken类型的对象。某一种动物能否发出叫声,只取决于它有没有makeSound方法,而不取决于它是否是某种类型的对象,这里不存在任何程度上的类型耦合。”
varmakeSound=function(animal){animal.sound();}varDuck=function(){}Duck.prototype.sound=function(){console.log('嘎嘎嘎')}varChicken=function(){}Chicken.prototype.sound=function(){console.log('咯咯咯')}makeSound(newDuck());// 嘎嘎嘎makeSound(newChicken());// 咯咯咯封装
封装的目的是将信息隐藏。Javascript只能依赖变量的作用域来实现封装特性。
封装包括:封装数据、封装实现、封装类型、封装变化。
原型模式和基于原型继承的JavaScript对象系统
在以类为中心的面向对象编程语言中,对象总是从类中创建而来。而在原型编程的思想中,一个对象是通过克隆另一个对象得到的。
原型模式的实现关键是语言本身是否提供了clone方法。ECMAScript 5提供了Object.create方法可以用来克隆对象。
varPlane=function(){this.blood=50;this.attackLevel=1;this.defenseLevel=1;}varplane=newPlane();plane.blood=500;plane.attackLevel=10;plane.defenseLevel=7;varclonePlane=Object.create(plane);console.log(clonePlane)// 输出:Object {blood: 500, attackLevel: 10, defenseLevel: 7}// 兼容IE8Object.create=Object.create||function(obj){varF=function(){};F.prototype=obj;returnnewF();}原型模式的真正目的并非在于需要得到一个一模一样的对象,而是提供了一种便捷的方式去创建某个类型的对象,克隆只是创建这个对象的过程和手段。
原型编程范型至少包括以下基本规则
- 所有的数据都是对象
- 要得到一个对象,不是通过实例化类,而是找到一个对象作为原型并克隆它
- 对象会记住原型
- 如果对象无法响应某个请求,它会把这个请求委托给它自己的原型。
基于原型链的委托机制就是原型继承的本质
JavaScript中的原型继承
JavaScript中的根对象是Object.prototype对象,Object.prototype对象是一个空的对象。我们在JavaScript遇到的每个对象,实际上都是从Object.prototype对象克隆而来的,Object.prototype对象就是它们的原型。
varobj1=newObject();varobj2={};console.log(Object.getPrototypeOf(obj1)===Object.prototype);// 输出:trueconsole.log(Object.getPrototypeOf(obj2)===Object.prototype);// 输出:true“在JavaScript语言里,我们并不需要关心克隆的细节,因为这是引擎内部负责实现的。我们所需要做的只是显式地调用var obj1 = new Object()或者var obj2 = {}。此时,引擎内部会从Object.prototype上面克隆一个对象出来,我们最终得到的就是这个对象。”
varPerson=function(name){this.name=name;}Person.prototype.getName=function(){returnthis.name;}varp=newPerson('sven');console.log(p.name)// svenconsole.log(p.getName)// svenconsole.log(Object.getPrototypeOf(p)===Person.prototype)// true就JavaScript的真正实现来说,其实并不能说对象有原型,而只能说对象的构造器有原型。对于“对象把请求委托给它自己的原型”这句话,更好的说法是对象把请求委托给它的构造器的原型。
Javascript给对象提供了一个__proto__的隐藏属性,某个对象的__proto__属性会指向它的构造器的原型对象。即{Constructor}.prototype
对象构造器的原型并不仅限于Object.prototype上,而是可以动态的指向其他对象。这样一来,当对象a需要借用对象b的能力时,可以有选择性地把对象a的构造器的原型指向对象b,从而达到继承的效果
varobj={name: 'sven'};varA=function(){}A.prototype=obj;vara=newA();console.log(a.name);// sven当我们期望得到一个“类”继承自另外一个“类”的效果时,往往会用下面的代码来模拟实现
varA=function(){}A.prototype={name: 'sven'};varB=function(){}B.prototype=newA()varb=newB();console.log(b.name)// svenECMAScript 6带来了新的Class语法。这让JavaScript看起来像是一门基于类的语言,但其背后仍是通过原型机制来创建对象。
classAnimal{constructor(name){this.name=name;}getName(){returnthis.name;}}classDogextendsAnimal{constructor(name){super(name)}speak(){return"woof";}}vardog=newDog("Scan");console.log(dog.getName()+" says: "+dog.speak());chapter2-this、call和apply
javascript的this总是指向一个对象。具体指向哪个对象是在运行时基于函数的执行环境动态绑定的。this的指向大致可以分为四种:
- 作为对象的方法调用
- 作为普通函数调用
- 构造器调用
Function.prototype.call或Function.prototype.apply调用
作为对象的方法调用
当函数作为对象的方法被调用时,this指向该对象。
varobj={a: 1,getA: function(){alert(this===obj);// 输出:truealert(this.a);// 输出: 1}}作为普通函数调用
当函数不作为对象的属性被调用时,也就是我们常说的普通函数方式,此时的this总是指向全局对象。
在ECMAScript 5的strict模式下,这种情况下的this已经被规定为不会指向全局对象,而是undefined
functionfunc(){"use strict"alert(this);// 输出:undefined}func();构造器调用
构造器的外表跟普通函数一模一样,它们的区别在于被调用的方式。当用new运算符调用函数时,该函数总会返回一个对象,通常情况下,构造器里的this就指向返回的这个对象。
如果构造器显式地返回了一个object类型的对象,那么此次运算结果最终会返回这个对象,而不是我们之前期待的this。如果构造器不显式地返回任何数据,或者是返回一个非对象类型的数据,就不会造成这个问题。
varMyClass=function(){this.name='sven';return{name: 'anne'}}varobj=newMyClass();console.log(obj.name);// 'anne'Function.prototype.call或Function.prototype.apply调用
Function.prototype.call或Function.prototype.apply可以动态地改变传入函数的this.
varobj1={name: 'sven',getName: function(){returnthis.name;}};varobj2={name: 'anne'};console.log(obj1.getName());// 输出: svenconsole.log(obj1.getName.call(obj2));// 输出:annecall和apply
apply接受两个参数,第一个参数指定了函数体内this对象的指向,第二个参数为一个带下标的集合,这个集合可以为数组,也可以为类数组,apply方法把这个集合中的元素作为参数传递给被调用的函数
varfunc=function(a,b,c){alert([a,b,c]);// 输出 [ 1, 2, 3 ]};func.apply(null,[1,2,3]);call传入的参数数量不固定,跟apply相同的是,第一个参数也是代表函数体内的this指向,从第二个参数开始往后,每个参数被依次传入函数
varfunc=function(a,b,c){alert([a,b,c]);// 输出 [ 1, 2, 3 ]};func.call(null,1,2,3);当使用call或者apply的时候,如果我们传入的第一个参数为null,函数体内的this会指向默认的宿主对象。但如果是在严格模式下,函数体内的this还是为null。
使用call或apply借用其他对象的方法
Math.max.apply(null,[6,4,8,1,2,7])// 8
call和apply的实际用途
改变this指向
varobj1={name: 'sven'};varobj2={name: 'anne'};window.name='window';vargetName=function(){alert(this.name);};getName();// 输出: windowgetName.call(obj1);// 输出: svengetName.call(obj2);// 输出: anneFunction.prototype.bind
模拟Function.prototype.bind的实现
Function.prototype.bind=function(){varself=this,context=[].shift.call(arguments),args=[].slice.call(arguments);returnfunction(){returnself.apply(context,[].concat.call(args,[].slice.call(arguments)))}}varobj={name: 'sven'}varfunc=function(a,b,c,d){console.log(this.name)// svenconsole.log([a,b,c,d])// [1,2,3,4]}.bind(obj,1,2);func(3,4)借用其他对象的方法
(function(){Array.prototype.push.call(arguments,3);console.log(arguments);// 输出[1,2,3]})(1,2);闭包
封装变量
varmult=(function(){varcache={};returnfunction(){varargs=[].join.call(arguments,',');if(cache[args]){returncache[args]}vara=1;for(vari=0,l=arguments.length;i<l;i++){a=a*arguments[i];}returncache[args]=a;}})()console.log(mult(1,2,3))// 6console.log(mult(1,2,3))// 6提炼函数是代码重构中的一种常见技巧。如果在一个大函数中有一些代码块能够独立出来,我们常常把这些代码块封装在独立的小函数里面。独立出来的小函数有助于代码复用,如果这些小函数有一个良好的命名,它们本身也起到了注释的作用。如果这些小函数不需要在程序的其他地方使用,最好是把它们用闭包封闭起来。
varmult=(function(){varcache={};varcalculate=function(){vara=1;for(vari=0,l=arguments.length;i<l;i++){a=a*arguments[i];}returna;}returnfunction(){varargs=[].join.call(arguments,',');if(cache[args]){returncache[args]}returncache[args]=calculate.apply(null,arguments);}})()延续局部变量的寿命
使用img进行数据上报
varreport=(function(){varimgs=[];returnfunction(){varimg=newImage();imgs.push(img);img.src=src;}})()面向对象实现命令模式
<html><body><buttonid="execute">execute</button><buttonid="undo">undo</button><script>varTv={open: function(){console.log('open tv');},close: function(){console.log('close tv');}}varOpenTvCommand=function(receiver){this.receiver=receiver;}OpenTvCommand.prototype.execute=function(){this.receiver.open();}OpenTvCommand.prototype.close=function(){this.receiver.close();}varsetCommand=function(command){document.getElementById('execute').onclick=function(){command.execute();}document.getElementById('undo').onclick=function(){command.close();}}setCommand(newOpenTvCommand(Tv))</script></body></html>判断一个数据是否是数组,在以往的实现中,可以基于鸭子类型的概念来判断,比如判断这个数据有没有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]"。
varisType=function(type){returnfunction(obj){returnObject.prototype.toString.call(obj)==='[object '+type+']';}}varisString=isType('String');varisNumber=isType('Number');varisArray=isType('Array');console.log(isArray([1,2,3,4]));// trueJavaScript实现AOP(面向切面编程)
AOP(面向切面编程)的主要作用是把一些跟核心业务逻辑模块无关的功能抽离出来,这些跟业务逻辑无关的功能通常包括日志统计、安全控制、异常处理等。把这些功能抽离出来之后,再通过“动态织入”的方式掺入业务逻辑模块中。
Function.prototype.before=function(beforeFn){var__self=this;returnfunction(){beforeFn.apply(this,arguments);return__self.apply(this,arguments);}}Function.prototype.after=function(afterFn){var__self=this;returnfunction(){varret=__self.apply(this,arguments);afterFn.apply(this,arguments);returnret;}}varfunc=function(){console.log(2);};func=func.before(function(){console.log(1);}).after(function(){console.log(3);});**JavaScript实现AOP(面向切面编程)**AOP(面向切面编程)的主要作用是把一些跟核心业务逻辑模块无关的功能抽离出来,这些跟业务逻辑无关的功能通常包括日志统计、安全控制、异常处理等。把这些功能抽离出来之后,再通过“动态织入”的方式掺入业务逻辑模块中。```javascriptFunction.prototype.before=function(beforeFn){var__self=this;returnfunction(){beforeFn.apply(this,arguments);return__self.apply(this,arguments);}}Function.prototype.after=function(afterFn){var__self=this;returnfunction(){varret=__self.apply(this,arguments);afterFn.apply(this,arguments);returnret;}}varfunc=function(){console.log(2);};func();函数柯里化
currying又称部分求值。一个currying的函数首先会接受一些参数,接受了这些参数之后,该函数并不会立即求值,而是继续返回另外一个函数,刚才传入的参数在函数形成的闭包中被保存起来。待到函数被真正需要求值的时候,之前传入的所有参数都会被一次性用于求值。
varcurring=function(fn){varargs=[];returnfunction(){if(arguments.length===0){fn.apply(this,args)}else{**JavaScript实现AOP(面向切面编程)**AOP(面向切面编程)的主要作用是把一些跟核心业务逻辑模块无关的功能抽离出来,这些跟业务逻辑无关的功能通常包括日志统计、安全控制、异常处理等。把这些功能抽离出来之后,再通过“动态织入”的方式掺入业务逻辑模块中。```javascriptFunction.prototype.before=function(beforeFn){var__self=this;returnfunction(){beforeFn.apply(this,arguments);return__self.apply(this,arguments);}}Function.prototype.after=function(afterFn){var__self=this;returnfunction(){varret=__self.apply(this,arguments);afterFn.apply(this,arguments);returnret;}}varfunc=function(){consol[].push.call(args,arguments);returnarguments.callee;}}}varcost=(function(){varmoney=0;returnfunction(){for(vari=0,l=arguments.length;i<l;i++){money+=arguments[i];}returnmoney;}})();varcost=currying(cost);cost(100);// 未真正求值cost(200);// 未真正求值cost(300);// 未真正求值console.log(cost());// 求值并输出:600反柯里化(uncurrying)的实现
Function.prototype.uncurrying=function(){varself=this;// self就是Array.prototype.pushreturnfunction(){// Array.prototype.push当做Function.prototype.call的this传进去returnFunction.prototype.call.apply(self,arguments)// arguments是[obj,'first'],apply接受数组形式的参数// 接下来被直接传入Function.prototype.call,call接受以逗号分隔的N个参数的形式// 所以arguments中的第一个参数会被当做call里面的this,其他参数一次传入call// 整句代码相当于 Array.prototype.push.call(obj,'first');}}varpush=Array.prototype.push.uncurrying();varobj={length: 1,a: 'test'};push(obj,‘first’);函数节流
varthrottle=function(fn,interval){varself=fn,timer,firstTime=true;returnfunction(){var_this=this,args=arguments;if(firstTime){self.apply(_this,args)returnfirstTime=false;}if(timer){returnfalse;}timer=setTimeout(function(){clearTimeout(timer);timer=null;self.apply(_this,args)},interval||500)}}
Chapter1 - 面向对象的Javascript
鸭子类型(duck typing):“如果它走起路来像鸭子,叫起来也是鸭子,那么它就是鸭子。”
多态: 同一操作作用于不同的对象上面,可以产生不同的解释和不同的执行结果。多态的思想实际上是把“做什么”和“谁去做”分离开来。要实现这一点,归根结底要先消除类型之间的耦合关系。
封装
原型模式和基于原型继承的JavaScript对象系统
原型模式的实现关键是语言本身是否提供了
clone方法。ECMAScript 5提供了Object.create方法可以用来克隆对象。原型编程范型至少包括以下基本规则
基于原型链的委托机制就是原型继承的本质
JavaScript中的原型继承
JavaScript中的根对象是
Object.prototype对象,Object.prototype对象是一个空的对象。我们在JavaScript遇到的每个对象,实际上都是从Object.prototype对象克隆而来的,Object.prototype对象就是它们的原型。Javascript给对象提供了一个
__proto__的隐藏属性,某个对象的__proto__属性会指向它的构造器的原型对象。即{Constructor}.prototypechapter2-this、call和apply
javascript的
this总是指向一个对象。具体指向哪个对象是在运行时基于函数的执行环境动态绑定的。this的指向大致可以分为四种:Function.prototype.call或Function.prototype.apply调用作为对象的方法调用
当函数作为对象的方法被调用时,
this指向该对象。作为普通函数调用
当函数不作为对象的属性被调用时,也就是我们常说的普通函数方式,此时的
this总是指向全局对象。构造器调用
构造器的外表跟普通函数一模一样,它们的区别在于被调用的方式。当用new运算符调用函数时,该函数总会返回一个对象,通常情况下,构造器里的
this就指向返回的这个对象。Function.prototype.call或Function.prototype.apply调用Function.prototype.call或Function.prototype.apply可以动态地改变传入函数的this.call和apply
当使用
call或者apply的时候,如果我们传入的第一个参数为null,函数体内的this会指向默认的宿主对象。但如果是在严格模式下,函数体内的this还是为null。使用
call或apply借用其他对象的方法call和apply的实际用途
改变this指向
Function.prototype.bind
模拟Function.prototype.bind的实现
借用其他对象的方法
闭包
封装变量
延续局部变量的寿命
使用
img进行数据上报面向对象实现命令模式
JavaScript实现AOP(面向切面编程)
AOP(面向切面编程)的主要作用是把一些跟核心业务逻辑模块无关的功能抽离出来,这些跟业务逻辑无关的功能通常包括日志统计、安全控制、异常处理等。把这些功能抽离出来之后,再通过“动态织入”的方式掺入业务逻辑模块中。
函数柯里化
currying又称部分求值。一个currying的函数首先会接受一些参数,接受了这些参数之后,该函数并不会立即求值,而是继续返回另外一个函数,刚才传入的参数在函数形成的闭包中被保存起来。待到函数被真正需要求值的时候,之前传入的所有参数都会被一次性用于求值。
反柯里化(uncurrying)的实现
函数节流