We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Javascript通过原型委托的方式实现继承。
动态类型语言的变量类型要到程序运行的时候,待变量被赋予某个值之后,才会具有某种类型。
var duck = { duckSinging: function(){ console.log( '嘎嘎嘎' ); } }; var chicken = { duckSinging: function(){ console.log( '嘎嘎嘎' ); } }; var choir = []; // 合唱团 var joinChoir = function( animal ){ if ( animal && typeof animal.duckSinging === 'function' ){ choir.push( animal ); console.log( '恭喜加入合唱团' ); console.log( '合唱团已有成员数量:' + choir.length ); } }; joinChoir( duck ); // 恭喜加入合唱团 joinChoir( chicken ); // 恭喜加入合唱团
“JavaScript对象的多态性是与生俱来的: “JavaScript的变量类型在运行期是可变的。一个JavaScript对象,既可以表示Duck类型的对象,又可以表示Chicken类型的对象。某一种动物能否发出叫声,只取决于它有没有makeSound方法,而不取决于它是否是某种类型的对象,这里不存在任何程度上的类型耦合。”
var makeSound = function (animal){ animal.sound(); } var Duck = function(){} Duck.prototype.sound = function(){ console.log('嘎嘎嘎') } var Chicken = function(){} Chicken.prototype.sound = function(){ console.log('咯咯咯') } makeSound(new Duck()); // 嘎嘎嘎 makeSound(new Chicken()); // 咯咯咯
封装的目的是将信息隐藏。Javascript只能依赖变量的作用域来实现封装特性。 封装包括:封装数据、封装实现、封装类型、封装变化。
在以类为中心的面向对象编程语言中,对象总是从类中创建而来。而在原型编程的思想中,一个对象是通过克隆另一个对象得到的。
原型模式的实现关键是语言本身是否提供了clone方法。ECMAScript 5提供了Object.create方法可以用来克隆对象。
clone
Object.create
var Plane = function(){ this.blood = 50; 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) // 输出:Object {blood: 500, attackLevel: 10, defenseLevel: 7} // 兼容IE8 Object.create = Object.create || function(obj){ var F = function() {}; F.prototype = obj; return new F(); }
原型模式的真正目的并非在于需要得到一个一模一样的对象,而是提供了一种便捷的方式去创建某个类型的对象,克隆只是创建这个对象的过程和手段。
原型编程范型至少包括以下基本规则
基于原型链的委托机制就是原型继承的本质
JavaScript中的根对象是Object.prototype对象,Object.prototype对象是一个空的对象。我们在JavaScript遇到的每个对象,实际上都是从Object.prototype对象克隆而来的,Object.prototype对象就是它们的原型。
Object.prototype
var obj1 = new Object(); var obj2 = {}; console.log( Object.getPrototypeOf( obj1 ) === Object.prototype ); // 输出:true console.log( Object.getPrototypeOf( obj2 ) === Object.prototype ); // 输出:true
“在JavaScript语言里,我们并不需要关心克隆的细节,因为这是引擎内部负责实现的。我们所需要做的只是显式地调用var obj1 = new Object()或者var obj2 = {}。此时,引擎内部会从Object.prototype上面克隆一个对象出来,我们最终得到的就是这个对象。”
var Person = function(name){ this.name = name; } Person.prototype.getName = function(){ return this.name; } var p = new Person('sven'); console.log(p.name) // sven console.log(p.getName) // sven console.log(Object.getPrototypeOf(p) === Person.prototype) // true
就JavaScript的真正实现来说,其实并不能说对象有原型,而只能说对象的构造器有原型。对于“对象把请求委托给它自己的原型”这句话,更好的说法是对象把请求委托给它的构造器的原型。
Javascript给对象提供了一个__proto__的隐藏属性,某个对象的__proto__属性会指向它的构造器的原型对象。即{Constructor}.prototype
__proto__
{Constructor}.prototype
对象构造器的原型并不仅限于Object.prototype上,而是可以动态的指向其他对象。这样一来,当对象a需要借用对象b的能力时,可以有选择性地把对象a的构造器的原型指向对象b,从而达到继承的效果
var obj = {name: 'sven'}; var A = function(){} A.prototype = obj; var a = new A(); console.log(a.name); // sven
当我们期望得到一个“类”继承自另外一个“类”的效果时,往往会用下面的代码来模拟实现
var A = function(){} A.prototype = {name: 'sven'}; var B = function(){} B.prototype = new A() var b = new B(); console.log(b.name) // sven
ECMAScript 6带来了新的Class语法。这让JavaScript看起来像是一门基于类的语言,但其背后仍是通过原型机制来创建对象。
class Animal { constructor(name){ this.name = name; } getName(){ return this.name; } } class Dog extends Animal { constructor(name){ super(name) } speak(){ return "woof"; } } var dog = new Dog("Scan"); console.log(dog.getName()+" says: "+ dog.speak());
javascript的this总是指向一个对象。具体指向哪个对象是在运行时基于函数的执行环境动态绑定的。this的指向大致可以分为四种:
this
Function.prototype.call
Function.prototype.apply
当函数作为对象的方法被调用时,this指向该对象。
var obj = { a: 1, getA: function(){ alert ( this === obj ); // 输出:true alert ( this.a ); // 输出: 1 } }
当函数不作为对象的属性被调用时,也就是我们常说的普通函数方式,此时的this总是指向全局对象。
在ECMAScript 5的strict模式下,这种情况下的this已经被规定为不会指向全局对象,而是undefined
undefined
function func(){ "use strict" alert ( this ); // 输出:undefined } func();
构造器的外表跟普通函数一模一样,它们的区别在于被调用的方式。当用new运算符调用函数时,该函数总会返回一个对象,通常情况下,构造器里的this就指向返回的这个对象。
如果构造器显式地返回了一个object类型的对象,那么此次运算结果最终会返回这个对象,而不是我们之前期待的this。如果构造器不显式地返回任何数据,或者是返回一个非对象类型的数据,就不会造成这个问题。
var MyClass = function(){ this.name = 'sven'; return { name: 'anne' } } var obj = new MyClass(); console.log(obj.name); // 'anne'
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
apply接受两个参数,第一个参数指定了函数体内this对象的指向,第二个参数为一个带下标的集合,这个集合可以为数组,也可以为类数组,apply方法把这个集合中的元素作为参数传递给被调用的函数
apply
var func = function( a, b, c ){ alert ( [ a, b, c ] ); // 输出 [ 1, 2, 3 ] }; func.apply( null, [ 1, 2, 3 ] );
call传入的参数数量不固定,跟apply相同的是,第一个参数也是代表函数体内的this指向,从第二个参数开始往后,每个参数被依次传入函数
call
var func = function( a, b, c ){ alert ( [ a, b, c ] ); // 输出 [ 1, 2, 3 ] }; func.call( null, 1, 2, 3 );
当使用call或者apply的时候,如果我们传入的第一个参数为null,函数体内的this会指向默认的宿主对象。但如果是在严格模式下,函数体内的this还是为null。
null
使用call或apply借用其他对象的方法
Math.max.apply(null,[6,4,8,1,2,7]) // 8
改变this指向
var obj1 = { name: 'sven' }; var obj2 = { name: 'anne' }; window.name = 'window'; var getName = function(){ alert ( this.name ); }; getName(); // 输出: window getName.call( obj1 ); // 输出: sven getName.call( obj2 ); // 输出: anne
Function.prototype.bind
模拟Function.prototype.bind的实现
Function.prototype.bind = function(){ var self = this, context = [].shift.call(arguments), args = [].slice.call(arguments); return function(){ return self.apply(context,[].concat.call(args, [].slice.call(arguments))) } } var obj = { name: 'sven' } var func = function(a,b,c,d){ console.log(this.name) // sven console.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 );
封装变量
var mult = (function(){ var cache = {}; return function(){ var args = [].join.call(arguments,','); if(cache[args]){ return cache[args] } var a = 1; for(var i = 0, l = arguments.length; i < l ; i++ ){ a = a* arguments[i]; } return cache[args] = a; } })() console.log(mult(1,2,3)) // 6 console.log(mult(1,2,3)) // 6
提炼函数是代码重构中的一种常见技巧。如果在一个大函数中有一些代码块能够独立出来,我们常常把这些代码块封装在独立的小函数里面。独立出来的小函数有助于代码复用,如果这些小函数有一个良好的命名,它们本身也起到了注释的作用。如果这些小函数不需要在程序的其他地方使用,最好是把它们用闭包封闭起来。
var mult = (function(){ var cache = {}; var calculate = function(){ var a = 1; for(var i = 0, l = arguments.length; i < l ; i++ ){ a = a* arguments[i]; } return a; } return function(){ var args = [].join.call(arguments,','); if(cache[args]){ return cache[args] } return cache[args] = calculate.apply(null,arguments); } })()
延续局部变量的寿命
使用img进行数据上报
img
var report = (function(){ var imgs = []; return function(){ var img = new Image(); imgs.push(img); img.src = src; } })()
面向对象实现命令模式
<html> <body> <button id="execute">execute</button> <button id="undo">undo</button> <script> var Tv = { open: function(){ console.log('open tv'); }, close: function(){ console.log('close tv'); } } var OpenTvCommand = function(receiver){ this.receiver = receiver; } OpenTvCommand.prototype.execute = function(){ this.receiver.open(); } OpenTvCommand.prototype.close = function(){ this.receiver.close(); } var setCommand = function(command){ document.getElementById('execute').onclick = function(){ command.execute(); } document.getElementById('undo').onclick = function(){ command.close(); } } setCommand(new OpenTvCommand(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]"。
var isType = function(type){ return function(obj){ return Object.prototype.toString.call(obj) === '[object '+type+']'; } } var isString = isType('String'); var isNumber = isType('Number'); var isArray = isType('Array'); console.log(isArray([1,2,3,4])); // true
JavaScript实现AOP(面向切面编程)
AOP(面向切面编程)的主要作用是把一些跟核心业务逻辑模块无关的功能抽离出来,这些跟业务逻辑无关的功能通常包括日志统计、安全控制、异常处理等。把这些功能抽离出来之后,再通过“动态织入”的方式掺入业务逻辑模块中。
Function.prototype.before = function(beforeFn){ var __self = this; return function(){ beforeFn.apply(this,arguments); return __self.apply(this,arguments); } } Function.prototype.after = function(afterFn){ var __self = this; return function(){ var ret = __self.apply(this,arguments); afterFn.apply(this,arguments); return ret; } } var func = function(){ console.log( 2 ); }; func = func.before(function(){ console.log( 1 ); }).after(function(){ console.log( 3 ); });**JavaScript实现AOP(面向切面编程)** AOP(面向切面编程)的主要作用是把一些跟核心业务逻辑模块无关的功能抽离出来,这些跟业务逻辑无关的功能通常包括日志统计、安全控制、异常处理等。把这些功能抽离出来之后,再通过“动态织入”的方式掺入业务逻辑模块中。 ```javascript Function.prototype.before = function(beforeFn){ var __self = this; return function(){ beforeFn.apply(this,arguments); return __self.apply(this,arguments); } } Function.prototype.after = function(afterFn){ var __self = this; return function(){ var ret = __self.apply(this,arguments); afterFn.apply(this,arguments); return ret; } } var func = function(){ console.log( 2 ); }; func();
函数柯里化
currying又称部分求值。一个currying的函数首先会接受一些参数,接受了这些参数之后,该函数并不会立即求值,而是继续返回另外一个函数,刚才传入的参数在函数形成的闭包中被保存起来。待到函数被真正需要求值的时候,之前传入的所有参数都会被一次性用于求值。
var curring = function(fn){ var args = []; return function(){ if(arguments.length===0){ fn.apply(this,args) }else{**JavaScript实现AOP(面向切面编程)** AOP(面向切面编程)的主要作用是把一些跟核心业务逻辑模块无关的功能抽离出来,这些跟业务逻辑无关的功能通常包括日志统计、安全控制、异常处理等。把这些功能抽离出来之后,再通过“动态织入”的方式掺入业务逻辑模块中。 ```javascript Function.prototype.before = function(beforeFn){ var __self = this; return function(){ beforeFn.apply(this,arguments); return __self.apply(this,arguments); } } Function.prototype.after = function(afterFn){ var __self = this; return function(){ var ret = __self.apply(this,arguments); afterFn.apply(this,arguments); return ret; } } var func = function(){ consol [].push.call(args,arguments); return arguments.callee; } } } var cost = (function(){ var money = 0; return function(){ for ( var i = 0, l = arguments.length; i < l; i++ ){ money += arguments[ i ]; } return money; } })(); var cost = currying( cost ); cost( 100 ); // 未真正求值 cost( 200 ); // 未真正求值 cost( 300 ); // 未真正求值 console.log( cost() ); // 求值并输出:600
反柯里化(uncurrying)的实现
Function.prototype.uncurrying = function(){ var self = this; // self就是Array.prototype.push return function(){ // Array.prototype.push当做Function.prototype.call的this传进去 return Function.prototype.call.apply(self,arguments) // arguments是[obj,'first'],apply接受数组形式的参数 // 接下来被直接传入Function.prototype.call,call接受以逗号分隔的N个参数的形式 // 所以arguments中的第一个参数会被当做call里面的this,其他参数一次传入call // 整句代码相当于 Array.prototype.push.call(obj,'first'); } } var push = Array.prototype.push.uncurrying(); var obj = { length: 1, a: 'test' }; push( obj, ‘first’ );
函数节流
var throttle = function(fn,interval){ var self = fn, timer, firstTime = true; return function(){ var _this = this, args = arguments; if(firstTime){ self.apply(_this, args) return firstTime = false; } if(timer){ return false; } timer = setTimeout(function(){ clearTimeout(timer); timer = null; self.apply(_this,args) },interval||500) } }
The text was updated successfully, but these errors were encountered:
No branches or pull requests
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}.prototype
chapter2-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)的实现
函数节流
The text was updated successfully, but these errors were encountered: