JS创建对象的几种模式

浅谈 JS 创建对象的 8 种模式

2015/10/16 · JavaScript
· 对象

原文出处: Tomson   

  • Objct 模式
  • 工厂模式
  • 构造器模式
  • 通过 Function 对象实现
  • prototype 模式
  • 构造器与原型方式的混合模式
  • 动态原型模式
  • 混合工厂模式

1.Object 模式

var o1 = {};//字面量的表现形式
var o2 = new Object;
var o3 = new Object();
var o4 = new Object(null);
var o5 = new Object(undefined);
var o6 = Object.create(Object.prototype);//等价于 var o = {};//即以 Object.prototype 对象为一个原型模板,新建一个以这个原型模板为原型的对象
//上面6种都是一样
//区别
var o7 = Object.create(null);//创建一个原型为 null 的对象

JS创建对象的几种模式及分析对比

问题:通过类可以创建多个具有相同属性和方法的对象,但ECMAScript中没有类的概念,怎样创建一个对象呢?

先看看JS里对象的定义

ECMA-262把对象定义为:“无序属性的集合,其属性可以包含基本值、对象或函数”。对象的每个属性或方法都有一个名字,每个名字都映射到一个值,也就是所谓的key:value,其中value可以是数据或函数。

工厂模式

由于在ECMAScript无法创建类,所以用函数的封装以特定接口创建对象

function creatPerson(name,age){
      var o = new Object();
      o.name = name;
      o.age = age;
      o.sayname = function(){
             alert(this.name);
      }
      return o;
}
//创建实例
var person = creatPerson("ice",22 );

问题:解决了创建多个相似对象的问题,但是不知道对象的类型

1.Object 模式

JavaScript

var o1 = {};//字面量的表现形式 var o2 = new Object; var o3 = new
Object(); var o4 = new Object(null); var o5 = new Object(undefined); var
o6 = Object.create(Object.prototype);//等价于 var o = {};//即以
Object.prototype 对象为一个原型模板,新建一个以这个原型模板为原型的对象
//区别 var o7 = Object.create(null);//创建一个原型为 null 的对象

1
2
3
4
5
6
7
8
var o1 = {};//字面量的表现形式
var o2 = new Object;
var o3 = new Object();
var o4 = new Object(null);
var o5 = new Object(undefined);
var o6 = Object.create(Object.prototype);//等价于 var o = {};//即以 Object.prototype 对象为一个原型模板,新建一个以这个原型模板为原型的对象
//区别
var o7 = Object.create(null);//创建一个原型为 null 的对象

在 chrome 里查看各个新建对象的区别:
图片 1

可以看出前6种模式创建出来的对象都是一样的,第七种不同点在于其虽然也为
Object
对象但其无任何属性(包括没有任何可以继承的属性,因为创建的时候没有指定其原型)

2.工厂模式

//工厂方法1 通过一个方法来创建对象 利用 arguments 对象获取参数设置属性(参数不直观,容易出现问题)
function createCar(){
    var oTemp = new Object();
    oTemp.name = arguments[0];//直接给对象添加属性,每个对象都有直接的属性
    oTemp.age = arguments[1];
    oTemp.showName = function () {
        alert(this.name);
    };//每个对象都有一个 showName 方法版本
    return oTemp;
}
createCar("tom").showName();//在 JS 中没有传递的实参,实际形参值为 undefined(这里的 age 为 undefined)
createCar("tim",80).showName();
alert(createCar("tom") instanceof Object);//true 判断对象是否 Object 类或子类

//工厂方法2 通过传参设置属性(参数直观明了)
function createCar(name,age){
    var oTemp = new Object();
    oTemp.name = name;//直接给对象添加属性,每个对象都有直接的属性
    oTemp.age = age;
    oTemp.showName = function () {
        alert(this.name);
    };//每个对象都有一个 showName 方法版本
    return oTemp;
}
createCar("tom").showName();
createCar("tim",80).showName();
alert(createCar("tom") instanceof Object);//true 判断对象是否 Object 类或子类

工厂模式

function createPerson(name, age, job){
    //创建对象
    var o = new Object();
    //添加属性
    o.name = name;
    o.age = age;
    o.job = job;
    //添加方法
    o.sayName = function(){
        console.log(this.name);
    };
    //返回对象
    return o;
}

实例化:

var person1 = createPerson('smith', 25, 'coder');

缺点:没有解决对象的识别问题.

创建一个对象有好几种方法,比如:我想创建一个person对象,这个人的名字叫Tom,他今年20岁了,是一名教师~

1.最简单的方法:object构造函数方法

//创建一个object实例

var person=new object();

//为它添加属性

person.name=”Tom”;

person.age=20;

person.job=”teacher”;

//为它添加方法

person.sayName=function(){

        console.log(this.name);

};

2.同样的对象,用对象字面量的语法可以写成这样

var person={

        name : “Tom”,

        age: 20,

        job : “teacher”,

        sayName : function(){

                console.log(this.name);

        }

}

//下面两行是测试的代码,可以忽略

person.sayName();//Tom

console.log(person.age);//20

注:上面两种方法虽然简单,但是有一个致命的缺点,你看出来了吗?

是的,他们可以用来创建单个对象,可是如果你想要很多类似的对象怎么办呢?比如说我想为班上的50个同学每个人都创建一个属于他们的对象,要是按照上面的方法一个一个写,估计我会累死的。而且代码很多都是重复的,只不过是换个值而已,没有一点技术含量更没效率。

构造函数模式

function Person(naem,age){
      this.name = name;
      this.age = age;
      this.sayname = function(){
             alert(this.name);
      }
}
//创建实例
var person = Person("ice",22 );

与工厂模式相比较

  • 没有显示创建对象 ;
  • 将属性和方法赋予给this对象;
  • 没有return语句;

经历的步骤
1.创建一个对象;
2.构造函数的作用域赋给新对象;
3.执行构造函数的代码;
4.返回新对象;

alert(person instanceof Object); //ture 继承自Object
alert(person instanceof Person); //ture
//作为普通函数调用
Person("ice",21);//添加到window对象
alert(window.name);//ice
//在另一个对象作用域中调用
var o = new Object();
Person.call(o,"ice",27);
alert(o.name);//ice

问题:不同实例,同名函数不相等,一个实例重新创建一遍函数

2.工厂模式

JavaScript

//工厂方法1 通过一个方法来创建对象 利用 arguments
对象获取参数设置属性(参数不直观,容易出现问题) function createCar(){ var
oTemp = new Object(); oTemp.name =
arguments[0];//直接给对象添加属性,每个对象都有直接的属性 oTemp.age =
arguments[1]; oTemp.showName = function () { alert(this.name);
};//每个对象都有一个 showName 方法版本 return oTemp; }
createCar(“tom”).showName();//在 JS 中没有传递的实参,实际形参值为
undefined(这里的 age 为 undefined) createCar(“tim”,80).showName();
alert(createCar(“tom”) instanceof Object);//true 判断对象是否 Object
类或子类

1
2
3
4
5
6
7
8
9
10
11
12
13
//工厂方法1 通过一个方法来创建对象 利用 arguments 对象获取参数设置属性(参数不直观,容易出现问题)
function createCar(){
    var oTemp = new Object();
    oTemp.name = arguments[0];//直接给对象添加属性,每个对象都有直接的属性
    oTemp.age = arguments[1];
    oTemp.showName = function () {
        alert(this.name);
    };//每个对象都有一个 showName 方法版本
    return oTemp;
}
createCar("tom").showName();//在 JS 中没有传递的实参,实际形参值为 undefined(这里的 age 为 undefined)
createCar("tim",80).showName();
alert(createCar("tom") instanceof Object);//true 判断对象是否 Object 类或子类

JavaScript

//工厂方法2 通过传参设置属性(参数直观明了) function createCar(name,age){
var oTemp = new Object(); oTemp.name =
name;//直接给对象添加属性,每个对象都有直接的属性 oTemp.age = age;
oTemp.showName = function () { alert(this.name); };//每个对象都有一个
showName 方法版本 return oTemp; } createCar(“tom”).showName();
createCar(“tim”,80).showName(); alert(createCar(“tom”) instanceof
Object);//true 判断对象是否 Object 类或子类

1
2
3
4
5
6
7
8
9
10
11
12
13
//工厂方法2 通过传参设置属性(参数直观明了)
function createCar(name,age){
    var oTemp = new Object();
    oTemp.name = name;//直接给对象添加属性,每个对象都有直接的属性
    oTemp.age = age;
    oTemp.showName = function () {
        alert(this.name);
    };//每个对象都有一个 showName 方法版本
    return oTemp;
}
createCar("tom").showName();
createCar("tim",80).showName();
alert(createCar("tom") instanceof Object);//true 判断对象是否 Object 类或子类

3.构造器模式

//构造器方法1
function Car(sColor,iDoors){  //声明为构造器时需要将函数名首字母大写
    this.color = sColor;      //构造器内直接声明属性
    this.doors = iDoors;
    this.showColor = function(){
        return this.color;
    };//每个 Car 对象都有自己的 showColor方法版本
    this.showDoor = function () {
        return this.doors;
    }
}

使用方法1的问题很明显,没办法是 showDoor
方法重用,每次新建一个对象就要在堆里新开辟一片内存空间.改进如下

//构造器方法2
function showDoor(){      //定义一个全局的 Function 对象
    return this.doors;
}

function Car(sColor,iDoors){//构造器
    this.color = sColor;      //构造器内直接声明属性
    this.doors = iDoors;
    this.showColor = function(){
        return this.color;
    };
    this.showDoor = showDoor();//每个 Car 对象共享同一个 showDoor 方法版本(方法有自己的作用域,不用担心变量被共享)
}

alert(new Car("red",2).showColor());//通过构造器创建一个对象并调用其对象方法

构造函数模式

function Person(name, age, job){    //Person也是普通的函数, 但便于与普通函数区分, 通常将构造函数首字母大写
    //添加属性
    this.name = name;
    this.age = age;
    this.job = job;
    //添加方法
    this.sayName = function(){
        console.log(this.name);
    };
}

通过对比工厂模式我们发现:

  1. 没有显示创建对象;
  2. 直接将属性和方法赋给了this对象,this就是new出来的对象;
  3. 没有return语句.

实例化:

var person1 = new Person('smith', 25, 'coder');

判断实例类型:

console.log(person1.constructor === Person);   //true
console.log(person1 instanceof Person);        //true
console.log(person1 instanceof Object);        //true

优点: 创建自定义的构造函数可以将实例标识为一种特定的类型

缺点: 每个方法都要在每个实例上重新创建一遍, 无法代码共用.

                                           解决方法来了!

3.仿照工厂模式创建对象:工厂模式抽象了创建具体对象的过程,我们也可以把创建对象的具体过程封装起来。用什么方法呢?函数!!!**

(其实就是把方法1的代码放到函数里,让它变得更灵活)

function createPerson(name,age,job){

        //创建一个object实例

        var o=new object();

        //为它添加属性

        o.name=name;

        o.age=age;

        o.job=job;

        //为它添加方法

        o.sayName=function(){

                console.log(this.name);

        };

        return o;//记着要把结果返回

   }             

现在你可以创建多个相似对象了,但是却没办法知道对象的类型,别着急,程序员的智慧是无穷的

4.用构造函数模式创建对象

function Person(name,age,job){

        this.name=name;

        this.age=age;

        this.job=job;

        this.sayName=function(){

                console.log(this.name);

        }

}

//为Tom创建对象

var person1=new Person(“Tom”,20,”teacher”);

//为Tom的学生创建对象

var person1=new Person(“Tommy”,12,”student”);

注:(1)在这种方法中,我们并没有用new操作符新建一个对象,实际上函数自己帮我们创建了一个对象,它的引用是this,this拿到了当前对象的地址,我们才能把属性添加到对象中去。

原型模式

创建一个函数,就会创建一个prototype属性(一个指向对象的指针)指向函数的原型对象;默认原型对象会获得一个constructor(构造函数)属性(共享的可以通过对象实例访问),包含一个指向prototype所在函数的指针;通过这个构造函数我们可以继续向原型对象添加其他属性和方法。创建自定义构造函数默认只会取到constructor属性,其余方法继承自Object

alrt(Person.prototype.isPrototypeOf(person1));//true
alrt(Object.getPrototypeOf(person1) == Person.prototype);//true

通过isPrototypeOf确定对象之间是否存在这种关系

alrt(Object.getPrototypeOf(person1) == Person.prototype);//true
alrt(Object.getPrototypeOf(person1。name);//"Nicholas"

通过getPrototypeOf返回对象的原型

代码读取对象属性的时候会执行搜索,首先从实例本身开始,如果找到给定的属性,则返回属性的值;没有找到继续搜索指针指向的原型对象找到,返回属性的值。

对象间关系

function Person(){
}
Person.prototype.name = "Nicholas";
Person.age = 29;
Person.protype.job = "Software Enginner0"
Person.prototype.sayName = function(){
   alert(this.name);
};
var Person1 = new Person();
var Person2 = new Person();
Person1 .name = "ice";
alert(Person1 .name);//ice 来自实例
alert(Person2 .name);//NIcholas 来自原型
alert(Person1.sayname == Person2.sayname);//true

我们为实例添加同名属性会屏蔽原型对象的同名属性,但不会修改。我们可以用delete删除实例属性重新访问原型的属性

alert(Person1.hasOwnProperty("name"));//true

用hasOwnProperty()(Object中继承)方法检测属性在实例中还是在原型中,当在实例中返回true

示例

示例

示例

单独实用“in”操作符无论在实例还是原型,可访问就返回true

function hasprototyProperty(object,name){
   return !object.hasOwnProperty(name) && (name in object);
}

hasOwnProperty()方法和in操作符可以同时使用确定属性的位置


for-in所有可以通过对象可访问可枚举的属性,以及屏蔽了原型中不可枚举的属性(有Enumerable标记的属性)都可以返回
IE8之前屏蔽了原型中不可枚举的属性不会出现包括(hasOwnProperty()、propertyIsEnumerable()、toLocalString()、toString()、valueOf())

Object.keys(Person.prototype);//获取 可枚举 属性字符串数组
Object.keys(person);//获取  可枚举  的实例属性
Object.getOwnPropertyNames(Person.prototype);获取  属性  字符串数组

简单的原型语法

function Person(){
}
Person.prototype = {
   name : "Nicholas",
   age : 29,
   job:"Software Engineer",
   sayName : function(){
      alert(this.name);  
   } 
}

将Person.prototype
设置为等于一个一对象字面量形式创建的新对象,结果相同,但是constructor不在指向Person

重写原型对象之前

重写原型对象之后

此时instanceof还能返回正确结果,constructor已经无法确定对象类型了

var newperson = new Person();

alert(newperson instanceof Object);//true
alert(newperson instanceof Person);//true
alert(newperson.constructor == Object);//true
alert(newperson.constructor == Person);//faluse

必要时可以设置constructor 属性指向适当的值而此时constructor
的Enumerable被设置为true;

function Person(){
}
Person.prototype = {
   constructor : Person,
   name : "Nicholas",
   age : 29,
   job:"Software Engineer",
   sayName : function(){
      alert(this.name);  
   } 
}
//实用与ECMAScript5兼容的浏览器
Object.defineProperty(Person.prototype,"constructor",{
   enumerable : false,
   value : Preson
})

function Person(){
}
var friend  = new Person();
Person.prototype = {
   constructor : Person,
   name : "Nicholas",
   age : 29,
   job:"Software Engineer",
   sayName : function(){
      alert(this.name);  
   } 
}
friend .sayName(); //error

虽然可以随时为原型添加属性和方法并且在实例中反映出来,但是重写原型时,由于调用构造函数时添加的是指向最初原型的prototype指针,而把原型修改为另一个对象,切断了构造函数与最初原型的联系。
实例中指针指向原型,不指向构造函数

重写对象之前

重写对象之后

原生引用(Object、Array、String等等)都在构造函数原型上定义了方法

alert(typeof Array.prototype.sort);//function
alert(typeof String.prototype.substring);//function

通过对象的原型不仅可以引用,也可以定义新方法
但是这样容易命名冲突,但是这样容易意外重写原生方法
如果原型中包含引用会出现问题
例如:

function Person(){
}
Person.prototype = {
   constructor : Person,
   name : "ice",
   friend : ["fire","purple"]
}
var person1 = new Person();
var person2 = new Person();
person1.friends.push("blue");
alert(person1.friends);//"fire,purple,blue"
alert(person2.friends);//"fire,purple,blue"
alert(person1.friends  == person2.friends);//true

3.构造器模式

JavaScript

//构造器方法1 function Car(sColor,iDoors){
//声明为构造器时需要将函数名首字母大写 this.color = sColor;
//构造器内直接声明属性 this.doors = iDoors; this.showColor = function(){
return this.color; };//每个 Car 对象都有自己的 showColor方法版本
this.showDoor = function () { return this.doors; } }

1
2
3
4
5
6
7
8
9
10
11
//构造器方法1
function Car(sColor,iDoors){  //声明为构造器时需要将函数名首字母大写
    this.color = sColor;      //构造器内直接声明属性
    this.doors = iDoors;
    this.showColor = function(){
        return this.color;
    };//每个 Car 对象都有自己的 showColor方法版本
    this.showDoor = function () {
        return this.doors;
    }
}

使用方法1的问题很明显,没办法是 showDoor
方法重用,每次新建一个对象就要在堆里新开辟一篇空间.改进如下

JavaScript

//构造器方法2 function showDoor(){ //定义一个全局的 Function 对象 return
this.doors; } function Car(sColor,iDoors){//构造器 this.color = sColor;
//构造器内直接声明属性 this.doors = iDoors; this.showColor = function(){
return this.color; }; this.showDoor = showDoor();//每个 Car
对象共享同一个 showDoor 方法版本(方法有自己的作用域,不用担心变量被共享)
} alert(new
Car(“red”,2).showColor());//通过构造器创建一个对象并调用其对象方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//构造器方法2
function showDoor(){      //定义一个全局的 Function 对象
    return this.doors;
}
 
function Car(sColor,iDoors){//构造器
    this.color = sColor;      //构造器内直接声明属性
    this.doors = iDoors;
    this.showColor = function(){
        return this.color;
    };
    this.showDoor = showDoor();//每个 Car 对象共享同一个 showDoor 方法版本(方法有自己的作用域,不用担心变量被共享)
}
 
alert(new Car("red",2).showColor());//通过构造器创建一个对象并调用其对象方法

上面出现的问题就是语义不够清除,体现不出类的封装性,改进为 prototype 模式

4.通过Function对象实现创建对象

我们知道每声明一个函数实际是创建了一个Function 实例 JS
函数.

function function_name(param1,param2){alert(param1);}
//等价于
var function_name = new Function("param1","pram2","alert(param1);");

var Car2 = new Function("sColor","iDoors",
         "this.color = sColor;"+
         "this.doors = iDoors;"+
         "this.showColor = function(){ return this.color; }"
);
alert(new Car2("blue",3).showColor());

原型模式

function Person(){}

//Person.prototype为原型对象,每个构造函数的protot属性指向其对应的原型对象,以下为原型对象添加属性和方法
Person.prototype.name = 'smith';
Person.prototype.age = 25;
Person.prototype.job = 'coder';
Person.prototype.sayName = function(){
    console.log(this.name);
}

实例化:

var person1 = new Person();

获取原型对象:

console.log(Object.getPrototypeOf(person1) === Person.prototype);   //true

判断原型对象:

console.log(Person.prototype isPrototypeOf(person1))    //true

若为实例添加的属性或方法时与原型对象里的属性或方法相同,
添加的属性或方法会屏蔽原型对象中的那个属性或方法

function Person(){}

Person.prototype.name = 'smith';
Person.prototype.age = 25;
Person.prototype.job = 'coder';
Person.prototype.sayName = function(){
    console.log(this.name);
};

var person1 = new Person();
var person2 = new Person();

person1.name = 'Lihua';
console.log(person1.name)   //'Lihua'
console.log(person2.name)   //'smith'

通过以上结果发现: person1.name属性屏蔽了原型对象里的name属性,
阻止了访问原型中的那个属性, 使用delete操作符删除实例属性后,
能够重新访问原型中的属性

function Person(){}
Person.prototype.name= 'smith';
Person.prototype.age = '25';
Person.prototype.job = 'coder';
Person.prototype.sayName = function(){
    console.log(this.name);
}

var person1 = new Person();
var person2 = new Person();

person1.name = 'Lihua';
console.log(person1.sayName());     //Lihua
console.log(person2.sayName());     //smith

delete person1.name;
console.log(person1.sayName());     //smith

delete Object.getPrototypeOf(person1).name;     //删除原型对象的name属性
console.log(person1.sayName());     //undefined
console.log(person2.sayName());     //undefined

判断属性是否存在于实例中:

person1.name = 'Lihua';
console.log(person1.hasOwnProperty('name'))     //ture

组合使用构造函数模式和原型模式(创建自定义类型最常用的方法)

函数构造模式用于定义实例属性,原型模式用于定义方法和共享属性

function Person(name,age){
   this.name = name;
   this.age = age;
   this.friends = ["fire","red"];
}
Person.prototype = {
   constructor : Person,
   sayName : function(){
      alert(this.name);
   }
}

var person1 = new Person("ice","21");
var person1 = new Person("blue","22");
person1.friends.push("purple");
alert(person1.friends);//"fire","red","purple"
alert(person2.friends);//"fire","red"
alert(person2.friends == person2.friends);//false
alert(person2.sayName== person2.sayName);//true

4.通过Function对象实现创建对象

我们知道每声明一个函数实际是创建了一个Function 实例 JS
函数.

JavaScript

function function_name(param1,param2){alert(param1);} //等价于 var
function_name = new Function(“param1″,”pram2″,”alert(param1);”);

1
2
3
function function_name(param1,param2){alert(param1);}
//等价于
var function_name = new Function("param1","pram2","alert(param1);");

JavaScript

var Car2 = new Function(“sColor”,”iDoors”, “this.color = sColor;”+
“this.doors = iDoors;”+ “this.showColor = function(){ return this.color;
}” ); alert(new Car2(“blue”,3).showColor());

1
2
3
4
5
6
var Car2 = new Function("sColor","iDoors",
         "this.color = sColor;"+
         "this.doors = iDoors;"+
         "this.showColor = function(){ return this.color; }"
);
alert(new Car2("blue",3).showColor());

5.prototype模式

  • 类通过 prototype 属性添加的属性与方法都是绑定在这个类的 prototype
    域(实际为一个 Prototype
    对象
    )中,绑定到这个域中的属性与方法只有一个版本,只会创建一次.

  • 类的实例对象可以直接像调用自己的属性一样调用该类的 prototype
    域中的属性与方法,类可以通过调用 prototype 属性来间接调用prototype
    域内的属性与方法.

注意:通过类实例化出对象后对象内无 prototype
属性,但对象可直接像访问属性一样的访问类的 prototype
域的内容,实例对象有个私有属性proto,proto属性内含有类的
prototype 域内的属性与方法

方法1
function Car3(){}//用空构造函数设置类名
Car3.prototype.color = "blue";//每个对象都共享相同属性
Car3.prototype.doors = 3;
Car3.prototype.drivers = new Array("Mike","John");
Car3.prototype.showColor = function(){
    alert(this.color);
};//每个对象共享一个方法版本,省内存。

var car3_1 = new Car3();
var car3_2 = new Car3();

alert(car3_1.color);//blue
alert(car3_2.color);//blue
alert(Car3.prototype.color);//blue

car3_1.drivers.push("Bill");
alert(car3_1.drivers);//"Mike","John","Bill"
alert(car3_2.drivers);//"Mike","John","Bill"
alert(Car3.prototype.drivers);//"Mike","John","Bill"

//直接修改实例对象的属性,解析器会先去找实例对象是否有这个属性(不会去找实例对象的 _proto_ 属性内的那些类的 prototype 属性,而是直接查看这个实例是否有对应的属性(与_proto_同级))
//如果没有则直接给这个实例对象添加该属性,但不会修改类的prototype域的同名属性,既实例对象的_proto_属性内的那些类 prototype 域属性不会被修改
car3_1.color = "red";//car3_1对象内无名为 color 的对象属性,故将该属性添加到该对象上

//解析器对实例对象读取属性值的时候会先查找该实例有无同名的直接属性
//如果没有,则查找__proto__属性内保存的那些 当前类的 prototype 域的属性
//有就返回,无则继续查找是否有原型链中的对应的方法属性
//有就返回,无则返回undefined
alert(car3_1.color);//red
alert(car3_2.color);//blue
alert(car3_2.color2);//undefined

//直接修改类的 prototype 域内的属性,不会影响该类的实例对象的对象属性,但会影响实例对象的_proto_属性(_proto_属性内存放的是类的 prototype 域的内容)
Car3.prototype.color = "black";
alert(car3_1.color);//red 该对象有同名的直接属性,故不会去_proto_属性内查找类的 prototype 域的属性
alert(car3_2.color);//black 受影响

//直接修改实例对象的方法,解析器会先去找实例对象是否有这个方法(不会去找实例对象的 _proto_ 属性内的那些类的 prototype 域的方法,而是直接查看这个实例是否有对应的方法(与_proto_同级))
//如果没有则直接给这个实例对象添加该方法,但不会修改类的prototype域的同名方法,既实例对象的_proto_属性内的那些类 prototype 域方法不会被修改
//car3_1对象内无名为 showColor 的对象方法属性,故将该方法属性添加到该对象上
car3_1.showColor = function () {
    alert("new function");
}
//解析器对实例对象调用方法属性的时候会先查找该实例有无同名的直接方法属性
//如果没有,则查找_proto_属性内保存的那些 当前类的 prototype 域的方法属性
//有就返回,无则继续查找是否有原型链中的对应的方法属性
//找到就返回,无则报错

car3_1.showColor();//new function
car3_2.showColor();//blue
car3_1.abcd();//直接报错

//直接修改类的 prototype 域内的方法属性,不会影响该类的实例对象的方法属性,但会影响实例对象的_proto_属性(_proto_属性内存放的是类的 prototype 域的内容)
Car3.prototype.showColor = function () {
    alert("second function");
}
car3_1.showColor();//new function 该对象有同名的方法属性,故不会去_proto_属性内查找类的 prototype 域的方法属性
car3_2.showColor();//second function 受影响

可以看出使用该方法虽然说打打减少了内存的浪费,但依旧有问题,某个对象的属性一旦改变,所有由该类实例化得到的对象的proto内属性值也会跟着变(实为引用),改进如下

原型的动态性

对原型对象所做的任何修改能够立即从实例上反映出来,
即使是先实例化后修改原型对象也是如此

var friend = new Person();
Person.prototype.sayHi = function(){
    console.log('hi');
}

friend.sayHi()      //依然能够执行

JS高程写到:

尽管对原型对象所做的任何修改能够立即在所有实例中反映出来,
但重写整个原型对象情况还是不一样的,
调用构造函数时会为实例添加一个指向最初原型的[[prototype]]指针,
把原型修改为另外一个对象就切断了构造函数与最初原型之间的联系

function Person(){}
var friend = new Person();

Person.prototype = {
    constructor: Person,
    name: 'Lihua',
    job: 'student',
    sayName: function(){
        console.log(this.name);
    }
};

friend.sayName();   //error

但觉得JS高程这种说法有点欠妥, 比如:

function Person(){}
Person.prototype.job = 'coder';
Person.prototype.sayJob = function(){
    console.log(this.job);
}
var friend = new Person();

Person.prototype = {
    constructor: Person,
    name: 'Lihua',
    job: 'student',
    sayName: function(){
        console.log(this.name);
    }
};
var person = new Person();

person.sayName();   //Lihua
friend.sayJob();    //coder
// person.sayJob();    error

我的理解是:

  1. 原型是一种动态动态的关系,添加一个新的属性或方法到原型中时,该属性或方法会立即对所有基于该原型创建的对象可见,哪怕是修改原型之前创建的对象;
  2. 将原型重写为另外一个对象后, 会影响重写后实例得到的对象,
    切断了构造函数与最初原型之间的联系;
    对于重写之前已经实例化的对象没有影响,
    因为这些对象里已经保存了一个[[prototype]]指针,
    该指针指向重写之前的原型对象;

动态原型模式

把所有信息都封装在构造函数中,通过在构造函数初始化原型,又保持同时使用构造函数和原型的优点。
(通过检查某个应该存在的方法是否有效决定是否初始化原型)

function Person(name,age){
   this.name = name;
   this.age = age;
   if(typeof this.sayName != "function"){
      Person.prototype.sayName = function(){
         alert(this.name);
      };
   }
}

var friend = new Person("ice",21);
friend.sayName();

只有在sayName()不存在的情况下(初次调用构造函数)才会添加到原型中,其中只需要检查一个待添加的属性或方法,而且可以通过instanceof操作符确定类型

5.prototype模式

  • 类通过 prototype 属性添加的属性与方法都是绑定在这个类的 prototype
    域(实际为一个 Prototype
    对象)中,绑定到这个域中的属性与方法只有一个版本,只会创建一次.
  • 类的实例对象可以直接像调用自己的属性一样调用该类的 prototype
    域中的属性与方法,类可以通过调用 prototype 属性来间接调用prototype
    域内的属性与方法.

注意:通过类实例化出对象后对象内无 prototype
属性,但对象可直接像访问属性一样的访问类的 prototype
域的内容,实例对象有个私有属性__proto__,__proto__属性内含有类的
prototype 域内的属性与方法

JavaScript

方法1 function Car3(){}//用空构造函数设置类名 Car3.prototype.color =
“blue”;//每个对象都共享相同属性 Car3.prototype.doors = 3;
Car3.prototype.drivers = new Array(“Mike”,”John”);
Car3.prototype.showColor = function(){ alert(this.color);
};//每个对象共享一个方法版本,省内存。 var car3_1 = new Car3(); var
car3_2 = new Car3(); alert(car3_1.color);//blue
alert(car3_2.color);//blue alert(Car3.prototype.color);//blue
car3_1.drivers.push(“Bill”);
alert(car3_1.drivers);//”Mike”,”John”,”Bill”
alert(car3_2.drivers);//”Mike”,”John”,”Bill”
alert(Car3.prototype.drivers);//”Mike”,”John”,”Bill”
//直接修改实例对象的属性,解析器会先去找实例对象是否有这个属性(不会去找实例对象的
_proto_ 属性内的那些类的 prototype
属性,而是直接查看这个实例是否有对应的属性(与_proto_同级))
//如果没有则直接给这个实例对象添加该属性,但不会修改类的prototype域的同名属性,既实例对象的_proto_属性内的那些类
prototype 域属性不会被修改 car3_1.color = “red”;//car3_1对象内无名为
color 的对象属性,故将该属性添加到该对象上
//解析器对实例对象读取属性值的时候会先查找该实例有无同名的直接属性
//如果没有,则查找__proto__属性内保存的那些 当前类的 prototype
域的属性 //有就返回,无则继续查找是否有原型链中的对应的方法属性
//有就返回,无则返回undefined alert(car3_1.color);//red
alert(car3_2.color);//blue alert(car3_2.color2);//undefined
//直接修改类的 prototype
域内的属性,不会影响该类的实例对象的对象属性,但会影响实例对象的_proto_属性(_proto_属性内存放的是类的
prototype 域的内容) Car3.prototype.color = “black”;
alert(car3_1.color);//red
该对象有同名的直接属性,故不会去_proto_属性内查找类的 prototype
域的属性 alert(car3_2.color);//black 受影响
//直接修改实例对象的方法,解析器会先去找实例对象是否有这个方法(不会去找实例对象的
_proto_ 属性内的那些类的 prototype
域的方法,而是直接查看这个实例是否有对应的方法(与_proto_同级))
//如果没有则直接给这个实例对象添加该方法,但不会修改类的prototype域的同名方法,既实例对象的_proto_属性内的那些类
prototype 域方法不会被修改 //car3_1对象内无名为 showColor
的对象方法属性,故将该方法属性添加到该对象上 car3_1.showColor =
function () { alert(“new function”); }
//解析器对实例对象调用方法属性的时候会先查找该实例有无同名的直接方法属性
//如果没有,则查找_proto_属性内保存的那些 当前类的 prototype
域的方法属性 //有就返回,无则继续查找是否有原型链中的对应的方法属性
//找到就返回,无则报错 car3_1.showColor();//new function
car3_2.showColor();//blue car3_1.abcd();//直接报错 //直接修改类的
prototype
域内的方法属性,不会影响该类的实例对象的方法属性,但会影响实例对象的_proto_属性(_proto_属性内存放的是类的
prototype 域的内容) Car3.prototype.showColor = function () {
alert(“second function”); } car3_1.showColor();//new function
该对象有同名的方法属性,故不会去_proto_属性内查找类的 prototype
域的方法属性 car3_2.showColor();//second function 受影响

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
方法1
function Car3(){}//用空构造函数设置类名
Car3.prototype.color = "blue";//每个对象都共享相同属性
Car3.prototype.doors = 3;
Car3.prototype.drivers = new Array("Mike","John");
Car3.prototype.showColor = function(){
    alert(this.color);
};//每个对象共享一个方法版本,省内存。
 
var car3_1 = new Car3();
var car3_2 = new Car3();
 
alert(car3_1.color);//blue
alert(car3_2.color);//blue
alert(Car3.prototype.color);//blue
 
car3_1.drivers.push("Bill");
alert(car3_1.drivers);//"Mike","John","Bill"
alert(car3_2.drivers);//"Mike","John","Bill"
alert(Car3.prototype.drivers);//"Mike","John","Bill"
 
//直接修改实例对象的属性,解析器会先去找实例对象是否有这个属性(不会去找实例对象的 _proto_ 属性内的那些类的 prototype 属性,而是直接查看这个实例是否有对应的属性(与_proto_同级))
//如果没有则直接给这个实例对象添加该属性,但不会修改类的prototype域的同名属性,既实例对象的_proto_属性内的那些类 prototype 域属性不会被修改
car3_1.color = "red";//car3_1对象内无名为 color 的对象属性,故将该属性添加到该对象上
 
//解析器对实例对象读取属性值的时候会先查找该实例有无同名的直接属性
//如果没有,则查找__proto__属性内保存的那些 当前类的 prototype 域的属性
//有就返回,无则继续查找是否有原型链中的对应的方法属性
//有就返回,无则返回undefined
alert(car3_1.color);//red
alert(car3_2.color);//blue
alert(car3_2.color2);//undefined
 
//直接修改类的 prototype 域内的属性,不会影响该类的实例对象的对象属性,但会影响实例对象的_proto_属性(_proto_属性内存放的是类的 prototype 域的内容)
Car3.prototype.color = "black";
alert(car3_1.color);//red 该对象有同名的直接属性,故不会去_proto_属性内查找类的 prototype 域的属性
alert(car3_2.color);//black 受影响
 
//直接修改实例对象的方法,解析器会先去找实例对象是否有这个方法(不会去找实例对象的 _proto_ 属性内的那些类的 prototype 域的方法,而是直接查看这个实例是否有对应的方法(与_proto_同级))
//如果没有则直接给这个实例对象添加该方法,但不会修改类的prototype域的同名方法,既实例对象的_proto_属性内的那些类 prototype 域方法不会被修改
//car3_1对象内无名为 showColor 的对象方法属性,故将该方法属性添加到该对象上
car3_1.showColor = function () {
    alert("new function");
}
//解析器对实例对象调用方法属性的时候会先查找该实例有无同名的直接方法属性
//如果没有,则查找_proto_属性内保存的那些 当前类的 prototype 域的方法属性
//有就返回,无则继续查找是否有原型链中的对应的方法属性
//找到就返回,无则报错
 
car3_1.showColor();//new function
car3_2.showColor();//blue
car3_1.abcd();//直接报错
 
//直接修改类的 prototype 域内的方法属性,不会影响该类的实例对象的方法属性,但会影响实例对象的_proto_属性(_proto_属性内存放的是类的 prototype 域的内容)
Car3.prototype.showColor = function () {
    alert("second function");
}
car3_1.showColor();//new function 该对象有同名的方法属性,故不会去_proto_属性内查找类的 prototype 域的方法属性
car3_2.showColor();//second function 受影响

可以看出使用该方法虽然说打打减少了内存的浪费,但依旧有问题,某个对象的属性一旦改变,所有由该类实例化得到的对象的__proto__内属性值也会跟着变(实为引用),改进如下

6.构造器方式与原型方式的混合模式

//每个对象有专属的属性不会与其他对象共享
function Car4(sColor,iDoors){
    this._color = sColor;//私有属性变量名称头加下划线标识
    this._doors = iDoors;
    this.drivers = new Array("Mike","John");//公有属性标识
}
//所有对象共享一个方法版本,减少内存浪费
Car4.prototype.showColor = function () {
    alert(this._color);
};

var car4_1 = new Car4("red",4);
var car4_2 = new Car4("blue",3);

car4_1.drivers.push("Bill");

alert(car4_1.drivers);//"Mike","John","Bill"
alert(car4_2.drivers);//"Mike","John"

原型对象的问题

尽管可以在实例上添加一个同名属性从而隐藏那些不需要共享的属性,
然而对于包含引用类型的值的属性来说, 问题就比较突出.

function Person(){}

Person.prototype = {
    constructor: Person,
    name: 'smith',
    job: 'coder',
    friends: ['shelby', 'court'],
    sayName: function(){
        console.log(this.name);
    }
};

var person1 = new Person();
var person2 = new Person();

person1.friends.push('van');

console.log(person1.friends);                       //'shelby,court,van'
console.log(person2.friends);                       //'shelby,court,van'
console.log(person1.friends === person2.friends);   //true

在这种情况下,person1person2friends属性完全相同,
这不符合常识(一个人的朋友怎么可能和另外一个人的朋友完全相同呢?).

优点: 所有实例共享原型对象的属性和方法

缺点: 原型模式的最大问题正如以上所述,
无法屏蔽原型中数据类型为引用类型的属性

寄生构造函数模式

创建一个函数,该函数的作用仅仅封装创建对象的代码,然后返回新创的对象
例如创建一个具有额外方法的特殊数组,不能直接修改Array构造函数

function SpecialArray(){
   var values = new Array();
   values.push.apply(values,arguments);
   values.toPipedString = function(){
      return this.ioni("|"); 
   }
   return values;
}
var colors = new SpecialArray("red","blue","green");
alert(colors.toPipedString());//"red|blue|green"

返回的对象与构造函数或构造函数的原型属性之间没有关系,因此不能依赖instanceof确定对象的类型。

6.构造器方式与原型方式的混合模式

JavaScript

//每个对象有专属的属性不会与其他对象共享 function Car4(sColor,iDoors){
this._color = sColor;//私有属性变量名称头加下划线标识 this._doors =
iDoors; this.drivers = new Array(“Mike”,”John”);//公有属性标识 }
//所有对象共享一个方法版本,减少内存浪费 Car4.prototype.showColor =
function () { alert(this._color); }; var car4_1 = new Car4(“red”,4);
var car4_2 = new Car4(“blue”,3); car4_1.drivers.push(“Bill”);
alert(car4_1.drivers);//”Mike”,”John”,”Bill”
alert(car4_2.drivers);//”Mike”,”John”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//每个对象有专属的属性不会与其他对象共享
function Car4(sColor,iDoors){
    this._color = sColor;//私有属性变量名称头加下划线标识
    this._doors = iDoors;
    this.drivers = new Array("Mike","John");//公有属性标识
}
//所有对象共享一个方法版本,减少内存浪费
Car4.prototype.showColor = function () {
    alert(this._color);
};
 
var car4_1 = new Car4("red",4);
var car4_2 = new Car4("blue",3);
 
car4_1.drivers.push("Bill");
 
alert(car4_1.drivers);//"Mike","John","Bill"
alert(car4_2.drivers);//"Mike","John"

这也是常用的创建对象方式之一

7.动态原型模式

function Car5(sColor,iDoors,iMpg){
    this.color = sColor;
    this.doors = iDoors;
    this.mpg = iMpg;
    this.drivers = new Array("Mike","John");

    //使用标志(_initialized)来判断是否已给原型赋予了任何方法,保证方法永远只被创建并赋值一次
    if(typeof Car5._initialized == "undefined"){//因为这里的标记是附加在类上,故如果后期直接对其进行修改,还是有可能出现再次创建的情况
        Car5.prototype.showColor = function () {//为Car5添加一个存放在 prototype 域的方法
            alert(this.color);
        };
        Car5._initialized = true;//设置一个静态属性
    }
}
var car5_1 = new Car5("red",3,25);
var car5_2 = new Car5("red",3,25);

构造函数模式+原型模式

function Person(name, age, job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.friends = ['shelby', 'court'];
}

Person.prototype = {
    constructor: Person,
    sayName: function(){
        console.log(this.name);
    }
}

var person1 = new Person('smith', 25, 'coder');
var person2 = new Person('Lihua', 28, 'worker');

person1.friends.push('van');
console.log(person1.friends);                           //"shelby, court, van"
console.log(person2.friends);                           //"shelby, court"
console.log(person1.friends === person2.friends);       //false
console.log(person1.sayName === person2.sayName);       //true

构造函数模式+原型模式结合了构造函数模式和原型模式的优点,
将需要实例单独拥有的属性放到构造函数里, 将需要实例共享的方法放到原型里,
这种模式是目前使用最广泛和认同度最高的一种创建自定义类型的方法.

稳妥构造函数模式

适用于安全环境,没有公共属性,禁止使用this和new,防止数据被其他应用程序改动时使用

function Person(name,age){
   var o = new Object();
   //定义私有
   o.sayName = function(){
      alert(name); 
   }
   return o;
}

除了调用sayName()方法之外,没有别的可以访问数据成员的方式。也不能向构造函数的原始数据传入新数据适合安全环境,也不能使用instanceof操作符。

7.动态原型模式

JavaScript

function Car5(sColor,iDoors,iMpg){ this.color = sColor; this.doors =
iDoors; this.mpg = iMpg; this.drivers = new Array(“Mike”,”John”);
//使用标志(_initialized)来判断是否已给原型赋予了任何方法,保证方法永远只被创建并赋值一次
if(typeof Car5._initialized ==
“undefined”){//因为这里的标记是附加在类上,故如果后期直接对其进行修改,还是有可能出现再次创建的情况
Car5.prototype.showColor = function () {//为Car5添加一个存放在 prototype
域的方法 alert(this.color); }; Car5._initialized =
true;//设置一个静态属性 } } var car5_1 = new Car5(“red”,3,25); var
car5_2 = new Car5(“red”,3,25);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function Car5(sColor,iDoors,iMpg){
    this.color = sColor;
    this.doors = iDoors;
    this.mpg = iMpg;
    this.drivers = new Array("Mike","John");
 
    //使用标志(_initialized)来判断是否已给原型赋予了任何方法,保证方法永远只被创建并赋值一次
    if(typeof Car5._initialized == "undefined"){//因为这里的标记是附加在类上,故如果后期直接对其进行修改,还是有可能出现再次创建的情况
        Car5.prototype.showColor = function () {//为Car5添加一个存放在 prototype 域的方法
            alert(this.color);
        };
        Car5._initialized = true;//设置一个静态属性
    }
}
var car5_1 = new Car5("red",3,25);
var car5_2 = new Car5("red",3,25);

这种模式使得定义类像强类型语言例如 java 等语言的定义模式

8.混合工厂模式

function Car6(){
    var oTempCar = new Object;
    oTempCar.color = "blue";
    oTempCar.doors = 4;
    oTempCar.showColor = function () {
        alert(this.color);
    };
    return oTempCar;
}
var car6 = new Car6();

由于在 Car6()构造函数内部调用了 new 运算符,所以将忽略第二个 new
运算符(位于构造函数之外),
在构造函数内部创建的对象被传递回变量car6,这种方式在对象方法的内部管理方面与经典方式(工厂方法)有着相同的问题.应尽量避免

动态原型模式

function Person(name, age, job){
    //属性
    this.name = name;
    this.age = age;
    this.job = job;

    //方法
    if(typeof this.sayName != 'function'){  //在sayName方法不存在的情况下,将其添加到原型中
        Person.prototype.sayName = function(){
            console.log(this.name);
        };
    }
}

var friend = new Person('smith', 25, 'coder');
friend.sayName();   //smith

动态原型模式把所有信息都封装到了构造函数中,
通过在构造函数中初始化原型(可选), 保持了同时使用构造函数和原型的优点.

8.混合工厂模式

JavaScript

function Car6(){ var oTempCar = new Object; oTempCar.color = “blue”;
oTempCar.doors = 4; oTempCar.showColor = function () {
alert(this.color); }; return oTempCar; } var car6 = new Car6();

1
2
3
4
5
6
7
8
9
10
function Car6(){
    var oTempCar = new Object;
    oTempCar.color = "blue";
    oTempCar.doors = 4;
    oTempCar.showColor = function () {
        alert(this.color);
    };
    return oTempCar;
}
var car6 = new Car6();

由于在 Car6()构造函数内部调用了 new 运算符,所以将忽略第二个 new
运算符(位于构造函数之外),
在构造函数内部创建的对象被传递回变量car6,这种方式在对象方法的内部管理方面与经典方式(工厂方法)有着相同的问题.应尽量避免

1 赞 3 收藏
评论

图片 2

寄生构造函数模式

function createPerson(name, age, job){
    //创建对象
    var o = new Object();
    //添加属性
    o.name = name;
    o.age = age;
    o.job = job;
    //添加方法
    o.sayName = function(){
        console.log(this.name);
    };
    //返回对象
    return o;
}

var person1 = new Person('smith', 25, 'coder');
friend.sayName();   //'smith

寄生构造函数模式除了调用方式与工厂模式不同外new Person(‘smith’, 25,
‘coder’)
,其他完全一样, 通过在构造函数末尾添加return语句,
重写了调用构造函数时返回的对象

寄生构造函数模式适合在特殊情况下为对象创建构造函数.
因为不推荐在产品化的程序中修改原生对象的原型,
但在某些场合下又的确需要创建一个具有额外方法的原生对象:

function SpecialArray(){
    //创建数组
    var values = new Array();
    //添加值
    values.push.apply(values, arguments);
    //添加方法
    values.toPipedString = function(){
        return this.join('|');
    }

    //返回具有额外方法的数组
    return values;
}

var colors = new SpecialArray('red', 'blue', 'green');
console.log(colors.toPipedString()) //'red|blue|green'

缺点: 返回的对象与构造函数或与构造函数的原型属性之间没有关系,
因为return语句重写了调用构造函数时返回的对象,
也即是构造函数返回的对象与在构造函数外部创建的对象没有什么不同,
不能依赖instanceof操作符确定对象类型

稳妥构造函数模式

function Person(name, age, job){
    //创建要返回的对象
    var o = new Object();

    //这里定义私有变量和函数

    //添加方法
    o.sayName = function(){
        console.log(name);
    }

    //返回对象
    return o;
}

发表评论

电子邮件地址不会被公开。 必填项已用*标注