xml地图|网站地图|网站标签 [设为首页] [加入收藏]

JS如何创建对象和继承对象,从本质认识JavaScri

从本质认识JavaScript的原型继承和类继承

2016/04/06 · JavaScript · 1 评论 · 继承

原文出处: 十年踪迹(@十年踪迹)   

JavaScript发展到今天,和其他语言不一样的一个特点是,有各种各样的“继承方式”,或者稍微准确一点的说法,叫做有各种各样的基于prototype的模拟类继承实现方式。

在ES6之前,JavaScript没有类继承的概念,因此使用者为了代码复用的目的,只能参考其他语言的“继承”,然后用prototype来模拟出对应的实现,于是有了各种继承方式,比如《JavaScript高级程序设计》上说的 原型链,借用构造函数,组合继承,原型式继承,寄生式继承,寄生组合式继承 等等

那么多继承方式,让第一次接触这一块的小伙伴们内心有点崩溃。然而,之所以有那么多继承方式,其实还是因为“模拟”二字,因为我们在说继承的时候不是在研究prototype本身,而是在用prototype和JS特性来模拟别的语言的类继承。

我们现在抛开这些种类繁多的继承方式,来看一下prototype的本质和我们为什么要模拟类继承。

1.背景介绍

JS创建对象的几种方法:工厂模式,构造函数模式,原型模式,混合模式,动态原型模式

复制代码 代码如下:

原型继承

“原型” 这个词本身源自心理学,指神话、宗教、梦境、幻想、文学中不断重复出现的意象,它源自民族记忆和原始经验的集体潜意识。

所以,原型是一种抽象,代表事物表象之下的联系,用简单的话来说,就是原型描述事物与事物之间的相似性.

想象一个小孩子如何认知这个世界:

当小孩子没见过老虎的时候,大人可能会教他,老虎啊,就像是一只大猫。如果这个孩子碰巧常常和邻居家的猫咪玩耍,那么她不用去动物园见到真实的老虎,就能想象出老虎大概是长什么样子。

betway 1

这个故事有个更简单的表达,叫做“照猫画虎”。如果我们用JavaScript的原型来描述它,就是:

JavaScript

function Tiger(){ //... } Tiger.prototype = new Cat(); //老虎的原型是一只猫

1
2
3
4
5
function Tiger(){
    //...
}
 
Tiger.prototype = new Cat(); //老虎的原型是一只猫

很显然,“照猫画虎”(或者反过来“照虎画猫”,也可以,取决孩子于先认识老虎还是先认识猫)是一种认知模式,它让人类儿童不需要在脑海里重新完全构建一只老虎的全部信息,而可以通过她熟悉的猫咪的“复用”得到老虎的大部分信息,接下来她只需要去到动物园,去观察老虎和猫咪的不同部分,就可以正确认知什么是老虎了。这段话用JavaScript可以描述如下:

JavaScript

function Cat(){ } //小猫喵喵叫 Cat.prototype.say = function(){ return "喵"; } //小猫会爬树 Cat.prototype.climb = function(){ return "我会爬树"; } function Tiger(){ } Tiger.prototype = new Cat(); //老虎的叫声和小猫不同,但老虎也会爬树 Tiger.prototype.say = function(){ return "嗷"; }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function Cat(){
 
}
//小猫喵喵叫
Cat.prototype.say = function(){    
  return "喵";
}
//小猫会爬树
Cat.prototype.climb = function(){
  return "我会爬树";
}
 
function Tiger(){
 
}
Tiger.prototype = new Cat();
 
//老虎的叫声和小猫不同,但老虎也会爬树
Tiger.prototype.say = function(){
  return "嗷";
}

所以,原型可以通过描述两个事物之间的相似关系来复用代码,我们可以把这种复用代码的模式称为原型继承。

什么是面向对象编程?

 

<script> Function.prototype.createInstance = function(){
var T = function(){};
T.prototype = this.prototype;
T.constructor = this;
var o = new T();
this.apply(o, arguments);
return o;
}</script>

类继承

几年之后,当时的小孩子长大了,随着她的知识结构不断丰富,她认识世界的方式也发生了一些变化,她学会了太多的动物,有喵喵叫的猫,百兽之王狮子,优雅的丛林之王老虎,还有豺狼、大象等等。

这时候,单纯的相似性的认知方式已经很少被使用在如此丰富的知识内容里,更加严谨的认知方式——分类,开始被更频繁使用。

betway 2

这时候当年的小孩会说,猫和狗都是动物,如果她碰巧学习的是专业的生物学,她可能还会说猫和狗都是脊索门哺乳纲,于是,相似性被“类”这一种更高程度的抽象表达取代,我们用JavaScript来描述:

JavaScript

class Animal{ eat(){} say(){} climb(){} ... } class Cat extends Animal{ say(){return "喵"} } class Dog extends Animal{ say(){return "汪"} }

1
2
3
4
5
6
7
8
9
10
11
12
class Animal{
    eat(){}
    say(){}
    climb(){}
    ...
}
class Cat extends Animal{
    say(){return "喵"}
}
class Dog extends Animal{
    say(){return "汪"}
}

“面向对象编程”(Object OrientedProgramming,缩写为OOP)是目前主流的编程范式。它的核心思想是将真实世界中各种复杂的关系,抽象为一个个对象,然后由对象之间的分工与合作,完成对真实世界的模拟。

1 在工厂模式中,在构造函数内部创建一个新对象,最后返回这个对象。当实例化时,我们不需要用new关键字,就像调用方法一样就可以实例化。

说下上面代码里面 T.constructor = this这句话,我感觉这句话没有什么实际作用,
本身T.constructor应该是为Funtion,为什么要给它设定为Funtion的实例呢,

原型继承和类继承

所以,原型继承和类继承是两种认知模式,本质上都是为了抽象(复用代码)。相对于类,原型更初级且更灵活。因此当一个系统内没有太多关联的事物的时候,用原型明显比用类更灵活便捷。

原型继承的便捷性表现在系统中对象较少的时候,原型继承不需要构造额外的抽象类和接口就可以实现复用。(如系统里只有猫和狗两种动物的话,没必要再为它们构造一个抽象的“动物类”)

原型继承的灵活性还表现在复用模式更加灵活。由于原型和类的模式不一样,所以对复用的判断标准也就不一样,例如把一个红色皮球当做一个太阳的原型,当然是可以的(反过来也行),但显然不能将“恒星类”当做太阳和红球的公共父类(倒是可以用“球体”这个类作为它们的公共父类)。

既然原型本质上是一种认知模式可以用来复用代码,那我们为什么还要模拟“类继承”呢?在这里面我们就得看看原型继承有什么问题——

主要概念为:把一组数据结构和处理它们的方法组成对象(object),把相同行为的对象归纳为类(class),通过类的封装(encapsulation)隐藏内部细节,通过继承(inheritance)实现类的特化(specialization)/泛化(generalization),通过多态(polymorphism)实现基于对象类型的动态分派(dynamicdispatch)。

工厂模式的缺点是容易和普通函数混淆,只能通过命名来确认它是一个 构造函数。不推荐使用这种模式。

复制代码 代码如下:

原型继承的问题

由于我们刚才前面举例的猫和老虎的构造器没有参数,因此大家很可能没发现问题,现在我们试验一个有参数构造器的原型继承:

JavaScript

function Vector2D(x, y){ this.x = x; this.y = y; } Vector2D.prototype.length = function(){ return Math.sqrt(this.x * this.x + this.y * this.y); } function Vector3D(x, y, z){ Vector2D.call(this, x, y); this.z = z; } Vector3D.prototype = new Vector2D(); Vector3D.prototype.length = function(){ return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z); } var p = new Vector3D(1, 2, 3); console.log(p.x, p.y, p.z, p.length(), p instanceof Vector2D);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function Vector2D(x, y){
  this.x = x;
  this.y = y;
}
Vector2D.prototype.length = function(){
  return Math.sqrt(this.x * this.x + this.y * this.y);
}
 
function Vector3D(x, y, z){
  Vector2D.call(this, x, y);
  this.z = z;
}
Vector3D.prototype = new Vector2D();
 
Vector3D.prototype.length = function(){
  return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z);
}
 
var p = new Vector3D(1, 2, 3);
console.log(p.x, p.y, p.z, p.length(), p instanceof Vector2D);

上面这段代码里面我们看到我们用 Vector2D 的实例作为 Vector3D 的原型,在 Vector3D 的构造器里面我们还可以调用 Vector2D 的构造器来初始化 x、y。

但是,如果认真研究上面的代码,会发现一个小问题,在中间描述原型继承的时候:

JavaScript

Vector3D.prototype = new Vector2D();

1
Vector3D.prototype = new Vector2D();

我们其实无参数地调用了一次 Vector2D 的构造器!

这一次调用是不必要的,而且,因为我们的 Vector2D 的构造器足够简单并且没有副作用,所以我们这次无谓的调用除了稍稍消耗了性能之外,并不会带来太严重的问题。

但在实际项目中,我们有些组件的构造器比较复杂,或者操作DOM,那么这种情况下无谓多调用一次构造器,显然是有可能造成严重问题的。

于是,我们得想办法克服这一次多余的构造器调用,而显然,我们发现我们可以不必要这一次多余的调用:

JavaScript

function createObjWithoutConstructor(Class){ function T(){}; T.prototype = Class.prototype; return new T(); }

1
2
3
4
5
function createObjWithoutConstructor(Class){
    function T(){};
    T.prototype = Class.prototype;
    return new T();    
}

上面的代码中,我们通过创建一个空的构造器T,引用父类Class的prototype,然后返回new T( ),来巧妙地避开Class构造器的执行。这样,我们确实可以绕开父类构造器的调用,并将它的调用时机延迟到子类实例化的时候(本来也应该这样才合理)。

JavaScript

function Vector2D(x, y){ this.x = x; this.y = y; } Vector2D.prototype.length = function(){ return Math.sqrt(this.x * this.x + this.y * this.y); } function Vector3D(x, y, z){ Vector2D.call(this, x, y); this.z = z; } Vector3D.prototype = createObjWithoutConstructor(Vector2D); Vector3D.prototype.length = function(){ return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z); } var p = new Vector3D(1, 2, 3); console.log(p.x, p.y, p.z, p.length(), p instanceof Vector2D);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function Vector2D(x, y){
  this.x = x;
  this.y = y;
}
Vector2D.prototype.length = function(){
  return Math.sqrt(this.x * this.x + this.y * this.y);
}
 
function Vector3D(x, y, z){
  Vector2D.call(this, x, y);
  this.z = z;
}
Vector3D.prototype = createObjWithoutConstructor(Vector2D);
 
Vector3D.prototype.length = function(){
  return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z);
}
 
var p = new Vector3D(1, 2, 3);
console.log(p.x, p.y, p.z, p.length(), p instanceof Vector2D);

这样,我们解决了父类构造器延迟构造的问题之后,原型继承就比较适用了,并且这样简单处理之后,使用起来还不会影响 instanceof 返回值的正确性,这是与其他模拟方式相比最大的好处。

Javascript是一种基于对象(object-based)的语言,遇到的东西几乎都是对象,但是它不是一种面对对象的语言。像其他语言里面的class(类),它就没办法直接用了。(听说ES 6可以用了,笔者一直学的ES5,6暂未研究,有兴趣的同学可以去看看教程)

//factory pattern

<script>
Function.prototype.$extends = function(p){
this.$super = p;
var fn = function(){};
fn.prototype = p.prototype;
this.prototype = new fn();
//这句是我自己加的,保证构造出子类实例的constructor依然指向子类的构造器函数
this.prototype.constructor=this;
//-----------------------------
return this;
};
function Animal(){
}
function Cat(){
}
Cat.$extends(Animal);
var bb=new Cat();
alert(bb.constructor);
//但是(this.prototype.constructor=this)这种做法通过bb这个对象无法回朔到Animal的原型
//下面语句依然返回Cat这个函数,而不是Animal
alert(bb.constructor.prototype.constructor)
</script>

模拟类继承

最后,我们利用这个原理还可以实现比较完美的类继承:

JavaScript

(function(global){"use strict" Function.prototype.extend = function(props){ var Super = this; //父类构造函数 //父类原型 var TmpCls = function(){ } TmpCls.prototype = Super.prototype; var superProto = new TmpCls(); //父类构造器wrapper var _super = function(){ return Super.apply(this, arguments); } var Cls = function(){ if(props.constructor){ //执行构造函数 props.constructor.apply(this, arguments); } //绑定 this._super 的方法 for(var i in Super.prototype){ _super[i] = Super.prototype[i].bind(this); } } Cls.prototype = superProto; Cls.prototype._super = _super; //复制属性 for(var i in props){ if(i !== "constructor"){ Cls.prototype[i] = props[i]; } } return Cls; } function Animal(name){ this.name = name; } Animal.prototype.sayName = function(){ console.log("My name is "+this.name); } var Programmer = Animal.extend({ constructor: function(name){ this._super(name); }, sayName: function(){ this._super.sayName(name); }, program: function(){ console.log("I"m coding..."); } }); //测试我们的类 var animal = new Animal("dummy"), akira = new Programmer("akira"); animal.sayName();//输出 ‘My name is dummy’ akira.sayName();//输出 ‘My name is akira’ akira.program();//输出 ‘I"m coding...’ })(this);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
(function(global){"use strict"
 
  Function.prototype.extend = function(props){
    var Super = this; //父类构造函数
 
    //父类原型
    var TmpCls = function(){
 
    }
    TmpCls.prototype = Super.prototype;
 
    var superProto = new TmpCls();
 
    //父类构造器wrapper
    var _super = function(){
      return Super.apply(this, arguments);
    }
 
    var Cls = function(){
      if(props.constructor){
        //执行构造函数
        props.constructor.apply(this, arguments);
      }
      //绑定 this._super 的方法
      for(var i in Super.prototype){
        _super[i] = Super.prototype[i].bind(this);
      }
    }
    Cls.prototype = superProto;
    Cls.prototype._super = _super;
 
    //复制属性
    for(var i in props){
      if(i !== "constructor"){
        Cls.prototype[i] = props[i];
      }
    }  
 
    return Cls;
  }
 
  function Animal(name){
    this.name = name;
  }
 
  Animal.prototype.sayName = function(){
    console.log("My name is "+this.name);
  }
 
  var Programmer = Animal.extend({
    constructor: function(name){
      this._super(name);
    },
    sayName: function(){
      this._super.sayName(name);
    },
    program: function(){
      console.log("I"m coding...");
    }
  });
  //测试我们的类
  var animal = new Animal("dummy"),
      akira = new Programmer("akira");
  animal.sayName();//输出 ‘My name is dummy’
  akira.sayName();//输出 ‘My name is akira’
  akira.program();//输出 ‘I"m coding...’
 
})(this);

可以比较一下ES6的类继承:

JavaScript

(function(global){"use strict" //类的定义 class Animal { //ES6中新型构造器 constructor(name) { this.name = name; } //实例方法 sayName() { console.log("My name is "+this.name); } } //类的继承 class Programmer extends Animal { constructor(name) { //直接调用父类构造器进行初始化 super(name); } sayName(){ super.sayName(); } program() { console.log("I"m coding..."); } } //测试我们的类 var animal = new Animal("dummy"), akira = new Programmer("akira"); animal.sayName();//输出 ‘My name is dummy’ akira.sayName();//输出 ‘My name is akira’ akira.program();//输出 ‘I"m coding...’ })(this);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
(function(global){"use strict"
 
  //类的定义
  class Animal {
    //ES6中新型构造器
      constructor(name) {
          this.name = name;
      }
      //实例方法
      sayName() {
          console.log("My name is "+this.name);
      }
  }
 
  //类的继承
  class Programmer extends Animal {
      constructor(name) {
        //直接调用父类构造器进行初始化
          super(name);
      }
      sayName(){
          super.sayName();
      }
      program() {
          console.log("I"m coding...");
      }
  }
  //测试我们的类
  var animal = new Animal("dummy"),
      akira = new Programmer("akira");
  animal.sayName();//输出 ‘My name is dummy’
  akira.sayName();//输出 ‘My name is akira’
  akira.program();//输出 ‘I"m coding...’
 
})(this);

2.知识剖析

function createPerson(name, age, job){
    var o = {};
    o.name = name;
    o.age = age;
    o.job = job;
    o.friends = ["Mike", "Sun"];
    o.sayName = function(){
        alert("factory pattern: " + this.name);
    }
    return o;
}

还有上面这句代码,我自己加了1句,修正了子类构造器依然指向子类函数,但是对象的原型链的回朔不能到达父类原型,解决办法是
去掉this.prototype.constructor=this;既不给原型设置constructor属性,而是给实例设置一个constructor属性,如下代码

总结

原型继承和类继承是两种不同的认知模式,原型继承在对象不是很多的简单应用模型里比类继承更加灵活方便。然而JavaScript的原型继承在语法上有一个构造器额外调用的问题,我们只要通过 createObjWithoutConstructor 来延迟构造器的调用,就能解决这个问题。

3 赞 8 收藏 1 评论

betway 3

2.1对象的概念

var Abby = createPerson("Abby", "22", "Softwarre Engineer");
Abby.sayName();

复制代码 代码如下:

因为JS是一个基于对象的语言,所以我们遇到的大多数东西几乎都是对象。例如函数就是一个对象,如果你要在js里面新建一个对象,这样写其实就是创建了一个object的实例。对象就是一个容器,封装了属性和方法。属性就是对象的状态,比如下面的name属性。方法就是写在对象里面的函数,也就是对象的行为,比如下面的sayName方法。

 

<script>
Function.prototype.$extends = function(p){
this.$super = p;
var fn = function(){};
fn.prototype = p.prototype;
this.prototype = new fn();
return this;
};
function Animal(){
}
function Cat(){
this.constructor= arguments.callee;
}
Cat.$extends(Animal);
var bb=new Cat();
alert(bb.constructor);
//这种做法可以通过bb这个对象回朔到Animal的原型
alert(bb.constructor.prototype.constructor)
</script>

var person = new object();

2 构造函数模式,用new关键字来实例化对象。与工厂方式相比,使用构造函数方式创建对象,无需在函数内部重新创建对象,而使用this指代,并且函数无需明确return。不推荐使用这种模式。

最后分析下constructor的实际作用

person.name = "Tom";

构造函数的缺点是不断的拷贝,每new一次就造出一个副本,每个方法都要在每个实例上重新创建一遍,显然这样是不行的,我们想要的是一种有些方法共享所有,有此方法私有,于是Eric发明了原型链。

复制代码 代码如下:

person.sayNmae = function() {

//constructor pattern
function Person(name, age, job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = function(){
        alert("constructor pattern: " + this.name);
    }
}

<script>
//定义函数
var f=function(){
}
//这里显示true,因为f的构造器是Funtion,f内部的原型属性_proto_被赋值为构造器的prototype也就是Function的prototype
//instanceof检查f内部的_proto_是否与Function.prototype有共同的结点,如果有则返回true
alert(f instanceof Function)
//obj是f的实例
var obj=new f;
//obj内部的原型属性_proto_在new f时被赋值为f.prototype,显然f.prototype与Function.prototype没有共同的结点,因此显示false
alert(obj instanceof Function)
//为了让obj成为Function的实例也就是(obj instanceof Function)显示true
//只需要f.prototype=Function.prototype
f.prototype=Function.prototype;
//但是我不推荐上面这种做法,因为对f.prototype的修改会破坏了Function.prototype,例如f.prototype.name="51js"会给Function的原型也加上1个name属性
//正确的做法应该是下面这样,这样诸如f.prototype.name的修改就不会破坏Function的原型了
f.prototype=new Function();
f.prototype.name="zhouyang";
/**关键是这里,再次调整constructor属性为f,维护constructor这种做法是为了保证obj能够正确回朔原型链,
*假如我们要获取obj内部的原型链,但只知道obj,不知道obj是怎么实例化来的,由于obj内部的_proto_属性不可见,那么我们要获取obj内部原形只能通过obj.constructor来获取构造器,然后再获取构造器的prototype
*1.如果我们加下面这句(f.prototype.constructor=f),回朔obj原型链
*只能回朔1层原型链也就是obj.constructor.prototype(子类原型)-->obj.constructor.prototype.constructor.prototype(依然是子类原型),这样只能回朔1层原型链
**/
f.prototype.constructor=f;
obj=new f;
alert("找到子类了---"+obj.constructor+"n"
+"找到的还是子类,无法找到父类---"+obj.constructor.prototype.constructor)
alert(obj instanceof Function)
/**2.如果我们用下面的方法在f定义里设置f的实例的constructor,而不是f原型的constructor
*就可以回朔2层原型链也就是 obj.constructor.prototype(子类原型)-->obj.constructor.prototype.constructor.prototype(父类原型)
*显然这种情况是符合对象原型继承链的情况的
*/
f=function(){
this.constructor=arguments.callee;
}
f.prototype=new Function();
f.prototype.name="zhouyang";
obj=new f;
alert("找到子类了---"+obj.constructor+"n"
+"找到父类了---"+obj.constructor.prototype.constructor)
alert(obj instanceof Function)
</script>

alert(this.name);

var Abby = new Person("Abby", "22", "Software Engineer");
Abby.sayName();

复制代码 代码如下:

}

 

<script>
//定义函数
var f=function(){
}
//这里显示true,因为f的构造器是Funtion,f内部的原型属性_proto_被赋值为构造器的prototype也就是Function的prototype
//instanceof检查f内部的_proto_是否与Function.prototype有共同的结点,如果有则返回true
alert(f instanceof Function)
//obj是f的实例
var obj=new f;
//obj内部的原型属性_proto_在new f时被赋值为f.prototype,显然f.prototype与Function.prototype没有共同的结点,因此显示false
alert(obj instanceof Function)
//为了让obj成为Function的实例也就是(obj instanceof Function)显示true
//只需要f.prototype=Function.prototype
f.prototype=Function.prototype;
//但是我不推荐上面这种做法,因为对f.prototype的修改会破坏了Function.prototype,例如f.prototype.name="51js"会给Function的原型也加上1个name属性
//正确的做法应该是下面这样,这样诸如f.prototype.name的修改就不会破坏Function的原型了
f.prototype=new Function();
f.prototype.name="zhouyang";
/**关键是这里,再次调整constructor属性为f,维护constructor这种做法是为了保证obj能够正确回朔原型链,
*假如我们要获取obj内部的原型链,但只知道obj,不知道obj是怎么实例化来的,由于obj内部的_proto_属性不可见,那么我们要获取obj内部原形只能通过obj.constructor来获取构造器,然后再获取构造器的prototype
*1.如果我们加下面这句(f.prototype.constructor=f),回朔obj原型链
*只能回朔1层原型链也就是obj.constructor.prototype(子类原型)-->obj.constructor.prototype.constructor.prototype(依然是子类原型),这样只能回朔1层原型链
**/
f.prototype.constructor=f;
obj=new f;
alert("找到子类了---"+obj.constructor+"n"
+"找到的还是子类,无法找到父类---"+obj.constructor.prototype.constructor)
alert(obj instanceof Function)
/**2.如果我们用下面的方法在f定义里设置f的实例的constructor,而不是f原型的constructor
*就可以回朔2层原型链也就是 obj.constructor.prototype(子类原型)-->obj.constructor.prototype.constructor.prototype(父类原型)
*显然这种情况是符合对象原型继承链的情况的
*/
f=function(){
this.constructor=arguments.callee;
}
f.prototype=new Function();
f.prototype.name="zhouyang";
obj=new f;
alert("找到子类了---"+obj.constructor+"n"
+"找到父类了---"+obj.constructor.prototype.constructor)
alert(obj instanceof Function)
</script>结论constructor的作用就是维护对象的原型链

2.2 工厂模式
“面向对象编程”的第一步,就是要生成“对象”。但是很多时候我们不得不面临重复生成很多对象的情况,如果我有一千个人要记录他们的信息,像上面这种方法写的话,大大增加了代码的重复量,为了解决这个问题,人们开始使用工厂模式的一种变体,写法如下页。虽然工厂模式解决了代码复用的问题,但是却没办法显示实例(person1)和对象o之间的关系,比如aler(person1 instanceof o);

 

向果果和winter赐教一下,不知理解的是否正确哈,另外我看大家常说的原型的污染到底指的是什么??
作用的话下面这个或许可以说明

代码演示:
function Person(name,age, job) {

3 原型模式,这里就要说到prototype。我们创建的每个函数都有有一个 prototype(原型)属性,它也是一个对象,它的用途是包含有特定类型的所有实例的属性和方法。不推荐使用这种模式。

复制代码 代码如下:

this.name = name;

下面例子,我们把所有方法一个个添加到prototype上,但由于prototype上方法属于一种共享,这些方法有些别人用的到,有些别人根本用不到,有些别人想用的没有的方法还要再次往prototype上添加。这样就不好了,所以最常用的模式其实是混合型的。

<script>
var f = function(x){}
f.prototype={};
alert((new f).constructor);
f.prototype.constructor=f;
alert((new f).constructor);
</script>

this.age = age;

//prototype pattern
function Abby(){}

您可能感兴趣的文章:

  • Javascript的构造函数和constructor属性
  • 理解Javascript_11_constructor实现原理
  • JavaScript constructor和instanceof,JSOO中的一对欢喜冤家
  • 深入分析js中的constructor和prototype
  • JavaScript类和继承 constructor属性
  • JavaScript中几个重要的属性(this、constructor、prototype)介绍
  • JavaScript中的prototype和constructor简明总结
  • javascript new后的constructor属性
  • 不用构造函数(Constructor)new关键字也能实现JavaScript的面向对象
  • javascript设计模式Constructor(构造器)模式

this.job = job;

Abby.prototype.name = "Abby";
Abby.prototype.age = "22";
Abby.prototype.sayName = function(){
    alert("prototype pattern: " + this.name);
}

this.sayName = function() {

var person1 = new Abby();
person1.sayName();

alert(this.name)

 

};

 

}

4 构造函数模式和原型模式的混合类型。将所有属性不是方法的属性定义在函数中(构造函数方式),将所有属性值为方法的属性利用prototype在函数之外定义(原型方式)。 推荐使用这种方式创建对象。

person1 = new Person("Tom",20,"Engineer");

//hybrid constructor & prototype pattern
function Student(name, sno){
  this.name = name;
  this.sno = sno;
  this.sayName = function(){
    alert("hybrid constructor & prototype pattern: " + this.name);
  }
}

person2 = new Person("Damon",22,"Waiter");

Student.prototype = {
  constructor : Student,
  teacher : ["mike", "abby"],
  sayTeacher : function(){
    alert("hybrid constructor & prototype pattern(teacher): " + this.teacher);
  }
}

2.2 构造函数

var zhangsan = new Student("zhangsan", "22");
var lisi = new Student("lisi", "23");
zhangsan.sayName();
lisi.sayName();
zhangsan.sayTeacher();
lisi.sayTeacher();

后来就出现了构造函数,用来创建特定类型的对象,可以将实例和对象联系起来,用到了JS中的“this”,写法如下:

 

这样对象和实例之间就有关系了,以new这种方式调用构造函数会经历4个步骤:

 

(1)创建一个新对象。

5 动态原型方式

(2)将构造函数的作用域赋给新对象(这个this就指向了这个新对象)。

动态原型方式可以理解为混合模式的一个特例。该模式中,属性为方法 的属性直接在函数中进行了定义,但是因为if从句从而保证创建该对象的实例时,属性的方法不会被重复创建。推荐使用这种模式。

(3)执行函数内代码(给对象添加属性)

//dynamic prototype pattern
function Person(){
  this.name = "Mike";
  this.age = 22;
}
if (typeof Person._lev == "undefined"){
   Person.prototype.lev = function(){
     return this.name;
   }
   Person._lev = true;
}

(4)返回新对象。

var x = new Person();
alert(x.lev());

代码演示:

 

function Person(name,age, job) {

 

this.name = name;

  1. 寄生构造函数模式

this.age = age;

//parasitic constructor pattern (hybird factory)
function Person1(name, age, job){
  var o = new Object();
  o.name = name;
  o.age = age;
  o.job = job;
  o.sayName = function(){
    alert(this.name);
  }
  return o;
}

this.job = job;

var mike = new Person1("Mike", 22, "Software Engineer");
mike.sayName();

this.sayName = function() {

 

alert(this.name)

 

};

7 稳妥构造函数模式,这种模式不用this,不用new,目的是安全,这是一种方法,不是主流

}

//durable constructor pattern
function Person2(name, age, job){
  var o = new Object;
betway,  o.name = name;
  o.age = age;
  o.job = job;
  o.sayName = function(){
    alert(name);
  }
  return o;
}

person1 = new Person("Tom",20,"Engineer");

var mike = Person2("Mike", 22, "Software Engineer");
mike.sayName();

person2 = new Person("Damon",22,"Waiter");

 

构造函数特点:


上面代码中,Persoon就是构造函数,它提供模板,用来生成对象实例。为了与普通函数区别,构造函数名字的第一个字母通常大写。

 

构造函数的两个特点:

JS中的继承主要依靠原型链。

1.函数体内部使用了this关键字,代表了所要生成的对象实例。

 

2.生成对象的时候,必需用new命令,调用函数。

每个构造函数都拥有一个原型对象,原型对象都包含一个指向构造函数的指针(constructor),实例都包含一个指向原型对象的内部指针(_proto_)。

如果忘了使用new命令,直接调用构造函数会导致构造函数变成普通函数,就不会生成实例对象,并且此时的this这时代表全局对象,将造成一些意想不到的结果。

如果对原型进行多次赋值,那么后面的赋值会覆盖前面的,也就是通过原型链只能继承离实例化最近的一个 原型对象。

var Vehicle = function (){

原型链继承的本质就是一个单链表的深度搜索。例如,原型对象(Son.prototype)等于另一个原型(Person)的实例(person1),那么此时的原型对象(Son.prototype)将包含一个指向另一个原型(Person.prototype)的指针,相应的,另有一个原型(Person.prototype)中也包含着一个指向另一个构造函数(Person())的指针。

this.price = 1000;

再如,另一个原型(Person.prototype)又是另一个类型(Person)的实例(person1),那么上述关系依旧成立,如此层层递进,就构成了实例与原型的链条。这就是所谓的原型链。

};

所有引用类型默认继承了Object类型,所有函数的默认原型都是Object的实例,因此默认原型都会包含一个内部指针,指向Object.prototype.这也正是自定义类型能通过使用toString()等默认方法的原因。

var v = Vehicle();

在通过原型链实现继承时,不能使用对象字面量创建原型对象,这样会重写原型链。

v.price

 

// Uncaught TypeError: Cannot read property 'price' of undefined

 

上面代码中,调用Vehicle构造函数时,忘了加上new命令。结果,price属性变成了全局变量,而变量v变成了undefined。

call函数的用法(可用于实现继承)

因此必须小心,记得使用new命令。

call([thisObj[,arg1[, arg2[,   [,.argN]]]]]),调用一个对象的一个方法,以另一个对象替换当前对象。

2.3原型和原型链

原型prototype

JavaScript的每个对象都继承另一个对象,后者称为“原型” (prototype)对象。只有null除外,它没有自己的原型对象。

原型对象上的所有属性和方法,都能被派生对象共享。这就是JavaScript继承机制的基本设计。

通过构造函数生成实例对象时,会自动为实例对象分配原型对象。每一个构造函数都有一个prototype属性,这个属性就是实例对象的原型对象。

原型链

对象的属性和方法,有可能是定义在自身,也有可能是定义在它的原型对象。由于原型本身也是对象,又有自己的原型,所以形成了一条原型链(prototype

chain)。比如,a对象是b对象的原型,b对象是c对象的原型,以此类推。

“原型链”的作用是,读取对象的某个属性时,JavaScript引擎先寻找对象本身的属性,如果找不到,就到它的原型去找,如果还是找不到,就到原型的原型去找。如果直到最顶层的Object.prototype还是找不到,则返回undefined。

需要注意的是,一级级向上,在原型链寻找某个属性,对性能是有影响的。所寻找的属性在越上层的原型对象,对性能的影响越大。如果寻找某个不存在的属性,将会遍历整个原型链。

利用原型(prototype)的继承特性,我们可以将我们的函数写成

function Person() {

};

Person.prototype.name = "Tom";

Person.prototype.age = "20";

Person.prototype.job = "engineer";

Person.prototype.sayName = function() {

alert(this.name);

};

var person1 = new Person();

var person2 = new Person();

alert(person1.sayName == person2.sayName); //true

因为原型的继承,person1和person2的prototype都指向Person的prototype,所以这两个函数其实是相等的。但是用工厂函数或者构造模式, alert(person1.sayName == person2.sayName);就绝对不会为真了。

奇淫巧技1:每次写属性都要加一个prototype是不是很麻烦,其实还有另外一种写法

function Person() {

}

Person.prototype = {

name : "Tom";

age  : "20";

job : "engineer";

sayName : function() {

alert(this.name);

}

}

var person1 = new Person();

var person2 = new Person();

alert(person1.sayName == person2.sayName); //true

call方法可以用来代替另一个对象调用一个方法。call方法可将一个函数的对象上下文从初始的上下文改变为由htisObj指定的新对象。如果没有提供thisObj参数,那么Global对象被用作thisObj。

2.4 构造函数的继承

让一个构造函数继承另一个构造函数,是非常常见的需求。

也有多种方法实现,各有优缺点。比如现在有一个动物对象的构造函数,和一个猫对象的构造函数。

function Animal() {

this.species = “动物”;

};

function Cat(name,color) {

this.name = name;

this.color = color;

}

如何才能使Cat继承Animal呢?

function Animal(name){
  this.name = name;
  this.showName = function(){
    alert(this.name);
  }
}

2.4.1 构造函数绑定

第一种方法也是最简单的方法,使用call或apply方法,将父对象的构造函数绑定在子对象上,即在子对象构造函数中加一行:

function Cat(name,color){

Animal.apply(this, arguments); //加的

this.name = name;

this.color = color;

}

var cat1 = new Cat("大毛","黄色");

alert(cat1.species); // 动物

function Cat(name){
  Animal.call(this, name);
}

2.4.2 prototype(原型)模式

第二种方法更常见,使用prototype属性。

如果"猫"的prototype对象,指向一个Animal的实例,那么所有"猫"的实例,就能继承Animal了。

Cat.prototype = new Animal();

Cat.prototype.constructor = Cat;

var cat1 = new Cat("大毛","黄色");

alert(cat1.species); // 动物

代码的第一行,我们将Cat的prototype对象指向一个Animal的实例。相当于将Cat原先的原型对象删除,重新赋一个Animal实例的值。但是任何一个prototype对象都有一个constructor属性,指向它的构造函数。这个时候Cat的构造函数也改变了,变成了Animal。

var cat = new Cat("Black Cat");
cat.showName();

2.4.2 prototype(原型)模式

所以我们需要“Cat.prototype.constructor = Cat”将Cat的构造函数重新指向为Cat,不然的话会很容易出问题。

这是很重要的一点,编程时务必要遵守。如果替换了prototype对象,

b.prototype = new a();

那么,下一步必然是为新的prototype对象加上constructor属性,并将这个属性指回原来的构造函数。b.prototype.constructor = b;

Animal.call(this) 的意思就是使用 Animal对象代替this对象,那么 Cat中不就有Animal的所有属性和方法了吗,Cat对象就能够直接调用Animal的方法以及属性了.

2.4.3 直接继承prototype(原型)

第三种方法是对第二种方法的改进。由于Animal对象中,不变的属性都可以直接写入Animal.prototype。所以,我们也可以让Cat()跳过 Animal(),直接继承Animal.prototype。现在我们将Animal对象改写

``

function Animal() {

Animal.prototype.species = "动物";

}

然后,将Cat的prototype对象,指向Animal的prototype对象,这样就完成了继承。

Cat.prototype = Animal.prototype;

Cat.prototype.constructor = Cat;

var cat1 = new Cat("大毛","黄色");

alert(cat1.species); // 动物

同样,如果使用多个call就可以实现多重继承。

2.4.3 直接继承prototype(原型)

与前一种方法相比,这样做的优点是效率比较高(不用执行和建立Animal的实例了),比较省内存。缺点是 Cat.prototype和Animal.prototype现在指向了同一个对象,那么任何对Cat.prototype的修改,都会反映到Animal.prototype。所以Animal.prototype的构造函数也变成了Cat。

这个时候我们就需要引入一个空对象作为中转的中介,无论Cat的constructor如何变,只会影响到中转对象F而无法影响到父对象Animal了。

var F = function(){};

F.prototype = Animal.prototype;

Cat.prototype = new F();

Cat.prototype.constructor = Cat;

 

2.4.3 直接继承prototype(原型)

然后我们将上述方法封装成为一个函数,使用起来就很方便了

function extend(Child, Parent) {

var F = function(){};

F.prototype = Parent.prototype;

Child.prototype = new F();

Child.prototype.constructor = Child;

Child.uber = Parent.prototype;

}

 

2.4.3 直接继承prototype(原型)

使用的时候方法如下:

extend(Cat,Animal);

var cat1 = new Cat("大毛","黄色");

alert(cat1.species); // 动物

奇淫巧技2:封装函数的时候怎么方便怎么写不必太过考虑语义化的东西,比如写个状态机,直接将状态用数字表示,这样比字符串的形式好判断多了。但是一点也不语义化。

3.常见问题

必须要声明new来创建实例对象吗?

new操作符创建实例的过程:创建一个新对象->将构造函数的作用域赋给新对象(因此this就指向了这个新对象)->执行构造函数的代码(为这个新对象添加属性)->返回新对象

4.解决方案

1.必须要声明new来创建实例对象吗?

为了保证构造函数必须与new命令一起使用,一个解决办法是,在构造函数内部使用严格模式,即第一行加上use strict。

``

function Fubar(foo, bar){

'use strict';

this._foo = foo;

this._bar = bar;

}

Fubar();

// TypeError: Cannot set property '_foo' of undefined

上面代码的Fubar为构造函数,use

strict命令保证了该函数在严格模式下运行。由于在严格模式中,函数内部的this不能指向全局对象,默认等于undefined,导致不加new调用会报错(JavaScript不允许对undefined添加属性)。

另一个解决办法,是在构造函数内部判断是否使用new命令,如果发现没有使用,则直接返回一个实例对象。

function Fubar(foo, bar){

if (!(this instanceof Fubar)) {

return new Fubar(foo, bar);

}

this._foo = foo;

this._bar = bar;

}

Fubar(1, 2)._foo // 1

(new Fubar(1, 2))._foo // 1

上面代码中的构造函数,不管加不加new命令,都会得到同样的结果。

>5.编码实战

用面对对象编程的思想写状态机

6.扩展思考

面向对象与面向过程的区别?

传统的过程式编程(procedural programming)由一系列函数或一系列指令组成;而面向对象编程的程序由一系列对象组成。

每一个对象都是功能中心,具有明确分工,可以完成接受信息、处理数据、发出信息等任务。因此,面向对象编程具有灵活性、代码的可重用性、模块性等特点,容易维护和开发,非常适合多人合作的大型应用型软件项目。

7.参考文献

参考一:

参考二:

href="http://www.ruanyifeng.com/blog/search.html?cx=016304377626642577906%3Ab_e9skaywzq&cof=FORID%3A11&ie=UTF-8&q=Javascript+%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%E7%BC%96%E7%A8%8B&sa.x=9&sa.y=8">阮一峰

参考三:《Javascript高级程序设计》chapter 6

8.更多讨论

new命令的原理?

构造函数中的return语句的作用?

面向对象编程的继承原理?

鸣谢

感谢大家观看

PTT链接

JS中的面向对象编程_腾讯视频


技能树.IT修真院

“我们相信人人都可以成为一个工程师,现在开始,找个师兄,带你入门,掌控自己学习的节奏,学习的路上不再迷茫”。

这里是技能树.IT修真院,成千上万的师兄在这里找到了自己的学习路线,学习透明化,成长可见化,师兄1对1免费指导。快来与我一起学习吧 !http://www.jnshu.com/login/1/96194340

本文由必威发布于Web前端,转载请注明出处:JS如何创建对象和继承对象,从本质认识JavaScri