https://www.cnblogs.com/diligenceday/p/4246515.html
https://blog.csdn.net/fuxiaohui/article/details/44910765
首先,推荐一篇博客豪情的博客JS提高: http://www.cnblogs.com/jikey/p/3604459.html ,里面的链接全是精华, 一般人我不告诉他;
我们会先从JS的基本的设计模式开始,由浅入深, 会描述prototype,__proto__,consturctor等基础知识和JS的常见继承方式, 以及四个类工厂的推荐和使用(包括JS.Class,prototype的类工厂,john resig写的一个简洁类工厂库,以及Pjs一个很飘逸的继承库,很飘逸-_-),最后有3个参考资料链接:,最后有我个人的视频,欢迎拍砖哇, ///(o)_(o)////。
工厂模式:因为使用用一个接口创建很多对象会产生大量的重复代码,为了解决这个问题,人们就开始使用工厂模式:
运行下面代码
<!DOCTYPE html> <html> <head> <title></title> <meta charset="utf-8"> </head> <body> <script> //最好所有的代码自己打一遍, 增加印象; function l ( arg ) { console.log( arg ); }; </script> <script> //工厂模式:因为使用用一个接口创建很多对象会产生大量的重复代码,为了解决这个问题,人们就开始使用工厂模式: function Person(hairs,face, eye) { var o = new Object(); o.hairs = hairs; o.face = face; o.eye = eye; o.say = function(){ console.log("say someting to me!"); }; return o; }; //我们通过 Person(0,1,2)就可以创建一个包含特定信息的Person对象, 以后要生成对象直接执行Person然后给他传参数就好了; //比如我们要生成10个Person, 很方便很快; var c = 10; while( c-- ) { console.log(Person(c,c,c)) }; </script> </body> </html>
构造函数模式:使用构造函数模式我们能少些更多代码,比如上面的工厂模式我们可以改写成:
运行下面代码
<!DOCTYPE html> <html> <head> <title></title> <meta charset="utf-8"> </head> <body> <script> //最好所有的代码自己打一遍, 增加印象; function l ( arg ) { console.log( arg ); }; </script> <script> function Person(hairs, face, eye) { this.hairs = hairs; this.face = face; this.eye = eye; }; //同样, 我们再生成10个小朋友 var c = 10; while( c-- ) { console.log( new Person(c,c,c) ); }; </script> </body> </html>
//知识点1: 那么工厂模式和构造函数模式有什么区别呢:
/*
* 没有显式地创建对象
* 直接把属性和方法赋值给了this
* 没有return语句
* 构造函数的前面有一个new;
* */
// 知识点2:
/*
* 通过new的构造函数会经过四个阶段:
* 1:创建一个新对象;
* 2:把this赋值给这个新对象
* 3:执行构造函数中的代码
* 4:返回新对象
* */
原型是神马? 原型就是公用的方法或者属性,这么理解最简单, 当然:
1、prototype本质上还是一个JavaScript对象;
2、每个函数都有一个默认的prototype属性;
3、通过prototype我们可以扩展Javascript的内建对象
一些代码弄懂原型的本质:
运行下面代码
<!DOCTYPE html> <html> <head> <title></title> <meta charset="utf-8"> </head> <body> <script> //最好所有的代码自己打一遍, 增加印象; function l ( arg ) { console.log( arg ); }; </script> <script> function Memo() {}; Memo.prototype.hehe = 1; Memo.prototype.shut = function() { console.log("miaomiao") }; var m0 = new Memo(); var m1 = new Memo(); console.log(m0.shut === m1.shut); //ture, 因为m0的shut和m1的shut就是指向了Memo.prototype.shut这个方法,所以他们相等; //Object.getPrototypeOf会返回实例的原型; console.log(Object.getPrototypeOf(m0)) //输出了:Memo {hehe: 1, shut: function} console.log( Memo.prototype.isPrototypeOf(m0) )// 输出: true; //原型对象有一个很大的问题要非常注意 ==》》 原型的属性和方法是被共享的, 比如: Memo.prototype.shut = "W_W"; l(m0.shut) //输出:W_W, 悲剧了吧, 本来原型上的shut是个方法, 被改成字符串以后, 实例上的shut也发生了改变; l(m1.shut) //输出:W_W; m0.__proto__.shut = 1111; //m0的__proto__指向了Memo.prototype.shut,__proto__在标准浏览器下面是不能枚举到的,但确实是存在的一个属性; l(m1.shut) //输出了1111 //只要原型上的属性或者方法被改了, 实例上的也会发生改变; </script> </body>
</html>
知识点:Object.getPrototypeOf;
Fn.prototype.isPrototypeOf( instance );
好的, 现在再来了解一下constructor, 如果你已经知道constructor是什么的话, 这段略过, constructor是默认指向创建当前对象的构造函数, 但是这里面有一些坑要注意, 比如你的原型prototype被改了, 实例的constructor就变了 ,
运行下面代码
<!DOCTYPE html> <html> <head> <title></title> <meta charset="utf-8"> </head> <body> <script> //最好所有的代码自己打一遍, 增加印象; function l ( arg ) { console.log( arg ); }; </script> <script> var Fn = function(){ //我是构造函数Fn; }; l("Fn的constructor"); l(Fn.prototype.constructor); /*输出function (){ //我是构造函数Fn; } */ var f = new Fn; l("f的constructor"); l(f.constructor); /*输出; * function (){ //我是构造函数Fn; }*/ //当函数创建的时候默认就为prototype新建一个constructor指向自己; l(Fn.constructor === Function); //输出true, Function这个构造函数是Function; l(Fn.constructor) // 输出function Function() { [native code] } ,意思是Function这个构造函数 ; l(Fn.constructor === Fn.__proto__.constructor) //输出true; Fn的constructor实际上是指向Fn.__proto__的; //好的, 现在重新定义一个Fn; var Fn = function(){ //我是构造函数Fn; }; Fn.prototype = {}; l(Fn.prototype.constructor) /*打印出了内部的代码, 为什么呢? 因为我们改变了Fn的原型, Fn的constructor指向了{}空对象的Contructor; * function Object() { [native code] } */ </script> </body> </html>
大家一定要懂的是实例上的__proto__就是指向原型上的prototype, 这样会少走一些弯路,可以节约更多的时间用来看片, 你懂的;
每一个函数新建的时候都有一个默认的prototype, prototype这个对象上面默认有一个指向自己的constructor;
prototype和__proto__的区别:__proto__是实例和Person.prototype之间的关系,而constructor是实例和Person之间的关系
运行下面代码
<!DOCTYPE html> <html> <head> <title></title> <meta charset="utf-8"> </head> <body> <script> //最好所有的代码自己打一遍, 增加印象; function l ( arg ) { console.log( arg ); }; </script> <script> var Fn = function() {}; Fn.prototype.hehe = 1; Fn.prototype.lala = 2; var f = new Fn; l(f) //输出了Fn {hehe: 1, lala: 2} ; l(f.__proto__) //输出了Fn {hehe: 1, lala: 2} ; l(Fn.prototype === f.__proto__) //输出了true, 这里要懂的东西是实例上的__proto__这个属性指向了构造函数的prototype; l(f === f.__proto__) //输出false; 这里要懂的是f是new出来的实例; //因为f上面的hehe和lala都是继承的属性, 所以这里面的log并没有被输出; for(var p in f){ l("输出所以属性:" + p); //输出所以属性:hehe demo.html:11; 输出所以属性:lala //过滤非私有属性; if( f.hasOwnProperty(p) ) l("输出私有属性" + p); //因为f没有私有属性,所以这边没有log出来; }; </script> </body> </html>
有了上面的基础, 我们开始说说JS的面向对象和继承吧;
1:组合使用构造器和原型模式, 这种模式是构造函数和原型混合的模式, 使用最广泛, 认同度也最高的一种模式, 也是最基础的模式;
运行下面代码
<!DOCTYPE html> <html> <head> <title></title> <meta charset="utf-8"> </head> <body> <script> //最好所有的代码自己打一遍, 增加印象; function l ( arg ) { console.log( arg ); }; </script> <script> function Duck( name ,word) { this.name = name; this.word = word; }; Duck.prototype.say = function() { console.log( this.name+" say : " + this.word ) }; var duck = new Duck("nono","hehe"); duck.say(); </script> </body> </html>
寄生构造模式; 听名字真的很玄乎..其实跟工厂模式一模一样的, 其实就是自定义模型的封装;
运行下面代码
<!DOCTYPE html> <html> <head> <title></title> <meta charset="utf-8"> </head> <body> <script> //最好所有的代码自己打一遍, 增加印象; function l ( arg ) { console.log( arg ); }; </script> <script> function Foxy(name , word) { var result = new Object(); result.name = name; result.word = word; return result; }; l( new Foxy("nono","say someting") ); //输出:Object {name: "nono", word: "say someting"} ; function Monkey( ) { var aResult = []; //技巧:通过apply把arguments保存到数组; aResult.push.apply(aResult, arguments); return aResult; }; l( new Monkey("nono","dino","kite","sam") ); //打印出了这个:["nono", "dino", "kite", "sam"]; //要注意的是使用寄生模式的返回的对象和构造函数一点关系都没有; </script> </body> </html>
JS的原型继承, 继承是依赖于原型链的;那么JS原型链是什么呢:
/* 这段话慢慢读, 从搞基程序设计三抄过来的,很重要, 实体书最好自己看一看哇;
* ECMAScript中描述了原型链的概念, 并将原型链作为实现继承的主要方法, 基本思想是利用引用类型继承另一个引用类型的属性和方法。
* 简单回顾一下构造函数,原型和实例的关系:每一个函数都有一个原型对象, 每一个原型对象都有一个指向构造函数的指针,
* 而实例包含了一个指向原型对象的内部(不可见的)指针。 那么我们让原型对象等于另一个类型的实例, 那么这个原型对象将会包含指向
* 另一个原型对象的指针,如果另一个原型对象又是指向了别的原型的一个实例, 这样层层嵌套, 就形成了原型链;
* */
那么我们利用原型的原型链相互继承来写一个基本的例子:
运行下面代码
<!DOCTYPE html> <html> <head> <title></title> <meta charset="utf-8"> </head> <body> <script> //最好所有的代码自己打一遍, 增加印象; function l ( arg ) { console.log( arg ); }; </script> <script> var Fn = function() { this.property = true; } Fn.prototype.getFnProperty = function() { console.log( this.property ); }; var SubFn = function() { this.subProperty = false; }; //SubFn继承了Fn的实例 SubFn.prototype = new Fn(); //为实例添加额外的实例方法; SubFn.prototype.getSubProperty = function(){ console.log(this.subProperty); }; var subFn = new SubFn(); subFn.getFnProperty(); //输出了true subFn.getSubProperty(); //输出了false /*现在subFn的constructor 是 function () { this.property = true; }; 所以要修正SubFn.prototype.constructor = SubFn */ </script> </body> </html>
原型式继承第二个例子:
运行下面代码
<!DOCTYPE html> <html> <head> <title></title> <meta charset="utf-8"> </head> <body> <script> //最好所有的代码自己打一遍, 增加印象; function l ( arg ) { console.log( arg ); }; </script> <script> // 首先, 准备一个方法; var inherit = function(o) { if(!typeof o === "object")return; function F () {} F.prototype = o; F.prototype.constructor = F; return new F(); }; var Fn = function() { this.property = true; } Fn.prototype.getFnProperty = function() { console.log( this.property ); }; var SubFn = function() { this.subProperty = false; }; //SubFn继承了Fn的实例 SubFn.prototype = new Fn(); //为实例添加额外的实例方法; SubFn.prototype.getSubProperty = function(){ console.log(this.subProperty); }; var subFn = new SubFn(); //这个方法的内部, 临时创建了一个构造函数, 然后将传入的对象作为这个构造函数的原型, 最后返回一个临时的新实例; //ECMASscript 5 有新增了一个Object.create 效果和inherit一模一样, 它可以接收第二个参数, // 第二个参数要通过defineProperties的方式设置,会覆盖原型的属性, 比如: Object.create(subFn, { getFnProperty: { value:1 } }); var Fn = function() {}; //如果我们inherit传对象; Fn.prototype = inherit( {0:0,1:1,2:2,3:3,4:4} ); l( new Fn ) //==>Fn {0: 0, 1: 1, 2: 2, 3: 3, 4: 4, constructor: function(){....} 继承了哦 //如果我们给inherit传一个构造函数的实例; Fn.prototype = inherit( new SubFn ); l(new Fn); </script> </body> </html>
有时候看到原型的各种引用会尿失禁, 引来引去的,坑爹啊, 不说了去洗裤子了....
寄生组合式继承
组合继承是JS的常用继承模式, 但是也有自己的不足, 组合继承最大的问题的无论是什么情况下, 都会两次调用超类的构造函数;
一个是在创建子类原型的时候, 另一个是在子类构造函数的内部, 那么子类的原型会包含所有超类实例的全部属性,
寄生组合式继承就是为了解决子类原型包含所有超类实例全部属性这个问题而存在的;
运行下面代码
<!DOCTYPE html> <html> <head> <title></title> <meta charset="utf-8"> </head> <body> <script> //最好所有的代码自己打一遍, 增加印象; function l ( arg ) { console.log( arg ); }; </script> <script> var inherit = function(o) { if(!typeof o === "object")return; function F () {} F.prototype = o; F.prototype.constructor = F; return new F(); }; //首先要准备inheritPrototype方法; var util = util || {}; util.inherit = inherit; util.inheritPrototype = function(subType, superType) { var _prototype = this.inherit( superType.prototype ); _prototype.constructor = subType; subType.prototype = _prototype; }; function F( name ) { this.name = name; this.type = "human"; this.habits = ["dance","code"]; }; F.prototype.laugh = function() { console.log("heha!"); }; var InheritF = function() { F.apply( this, arguments ); }; util.inheritPrototype(InheritF, F); InheritF.prototype.letsGo = function() { l("1,2,3,4") }; var nono = new InheritF("nono"); nono.habits.push("read books"); l(nono.habits) var nonono = new InheritF("nono"); l( nonono.habits ); //继承的方法千万种,万变不离其宗; </script> </body> </html>
JS各种使用了继承库的类库推荐, 可以加深印象:
首先, JS.Class 是一个mootools式的类工厂 基于 lunereaper<![[dawid.kraczkowski[at]gmail[dot]com]]>的项目进行修改, 让子类的实现更简洁;
运行下面代码
prototype这个( ▼-▼ )库以前牛逼着呢, 但是一百年河东一百年河西, prototype当前的版本是最新版本的1.7稳定版,prototype里面的类工厂创建的主代码也被我单独裁出来了, 源码也差不多, 源码有我加的注释,自己个人方便阅读, HTML代码的最后自己写的几种 创建类工厂的方式, 可以借鉴(我没看api, 源码就是api....)
运行下面代码
这款继承创建的作者是jQ的作者,你懂的, 不绕;
https://github.com/html5crew/simple-inheritance 《《== 这个是代码的源地址:
运行下面代码
执行 new Foo打印出来对象结构如下:
pjs这个类工厂的github地址是:git://github.com/jayferd/pjs ,挺有名的js类工厂库, 写了源码分析, 关于代码我就不吐槽了,你看了就懂了, 不过真心挺方便的;
运行下面代码
每一个人都要有一片属于自己的宁静天空, 在那里没有压力, 只有自己 , 和自己身体的对话;
js继承有5种实现方式:
1、继承第一种方式:对象冒充
function Parent(username){
this.username = username;
this.hello = function(){
alert(this.username);
}
}
function Child(username,password){
//通过以下3行实现将Parent的属性和方法追加到Child中,从而实现继承
//第一步:this.method是作为一个临时的属性,并且指向Parent所指向的对象,
//第二步:执行this.method方法,即执行Parent所指向的对象函数
//第三步:销毁this.method属性,即此时Child就已经拥有了Parent的所有属性和方法
this.method = Parent;
this.method(username);//最关键的一行
delete this.method;
this.password = password;
this.world = function(){
alert(this.password);
}
}
var parent = new Parent("zhangsan");
var child = new Child("lisi","123456");
parent.hello();
child.hello();
child.world();
2、继承第二种方式:call()方法方式
call方法是Function类中的方法
call方法的第一个参数的值赋值给类(即方法)中出现的this
call方法的第二个参数开始依次赋值给类(即方法)所接受的参数
function test(str){
alert(this.name + " " + str);
}
var object = new Object();
object.name = "zhangsan";
test.call(object,"langsin");//此时,第一个参数值object传递给了test类(即方法)中出现的this,而第二个参数"langsin"则赋值给了test类(即方法)的str
function Parent(username){
this.username = username;
this.hello = function(){
alert(this.username);
}
}
function Child(username,password){
Parent.call(this,username);
this.password = password;
this.world = function(){
alert(this.password);
}
}
var parent = new Parent("zhangsan");
var child = new Child("lisi","123456");
parent.hello();
child.hello();
child.world();
3、继承的第三种方式:apply()方法方式
apply方法接受2个参数,
A、第一个参数与call方法的第一个参数一样,即赋值给类(即方法)中出现的this
B、第二个参数为数组类型,这个数组中的每个元素依次赋值给类(即方法)所接受的参数
function Parent(username){
this.username = username;
this.hello = function(){
alert(this.username);
}
}
function Child(username,password){
Parent.apply(this,new Array(username));
this.password = password;
this.world = function(){
alert(this.password);
}
}
var parent = new Parent("zhangsan");
var child = new Child("lisi","123456");
parent.hello();
child.hello();
child.world();
4、继承的第四种方式:原型链方式,即子类通过prototype将所有在父类中通过prototype追加的属性和方法都追加到Child,从而实现了继承
function Person(){
}
Person.prototype.hello = "hello";
Person.prototype.sayHello = function(){
alert(this.hello);
}
function Child(){
}
Child.prototype = new Person();//这行的作用是:将Parent中将所有通过prototype追加的属性和方法都追加到Child,从而实现了继承
Child.prototype.world = "world";
Child.prototype.sayWorld = function(){
alert(this.world);
}
var c = new Child();
c.sayHello();
c.sayWorld();
5、继承的第五种方式:混合方式
混合了call方式、原型链方式
function Parent(hello){
this.hello = hello;
}
Parent.prototype.sayHello = function(){
alert(this.hello);
}
function Child(hello,world){
Parent.call(this,hello);//将父类的属性继承过来
this.world = world;//新增一些属性
}
Child.prototype = new Parent();//将父类的方法继承过来
Child.prototype.sayWorld = function(){//新增一些方法
alert(this.world);
}
var c = new Child("zhangsan","lisi");
c.sayHello();
c.sayWorld();
最后提供一些参考的链接:
javascript 类属性、类方法、类实例、实例属性、实例方法、prototype、__proto__ 测试与小结:
http://www.cnblogs.com/mrsunny/archive/2011/05/09/2041185.html
JS的构造函数:
http://www.cnblogs.com/jikey/archive/2011/05/13/2045005.html
浅析Javascript原型继承: