JavaScript高级程序设计(反刍) 4

第六章(上):

对象是一种无序属性的集合,其属性值可以包含基本值、对象、函数。对象的属性/方法都存在名字,而这个名字每一个都映射一个值,这个值可以是数据,也可以是函数。
对象字面量是创建对象的首选方式,这样显得更具有封装性

属性:(个人感觉不是很重要,只是为了增强对于属性的理解添加的)
ECMAScript中的属性分两种:数据属性访问器属性

数据属性:

  1. [[Configurable]]:表示能否通过delete删除属性,默认为true
  2. [[Enumerbale]]:表示能否通过for-in循环返回属性,默认为true
  3. [[Writeable]]:表示能否修改属性的值,默认为true
  4. [[Value]]:属性的数据值,默认为undefined

想要修改这里面的属性,必须使用ES5中定义的Object.defineProperty()方法,该方法接收三个参数:属性所在的对象、属性的名字、描述符对象。描述符对象的属性必须是:configurable、enumerbale、writeable、value,可以设置一个或多个

var person = {
};
object.defineProperty(person, “name”, {
	write: false,
	value: "Nicholas"                    //不允许该值被修改
});
console.log(person.name);                //Nicholas
person.name = "apple";
console.log(person.name);               //Nicholas

当已经出现禁止修改属性,强行修改value时,非严格模式先会忽略该操作,严格模式下报错。尤其是configurable属性,当设置为false时,属性定义不可配置,就不能再转化为可配置的了

访问器属性:

  1. [[Configurable]]:表示能否通过delete删除属性,默认为true
  2. [[Enumerbale]]:表示能否通过for-in循环返回属性,默认为true
  3. [[Get]]:在独去属性时调用该函数,默认值undefined
  4. [[Set]]:在写入属性时调用该函数,默认值为undefined

这个属性和前面的数据属性相同,也不允许直接定义,需要使用Object.defineProperty()来定义
定义多个属性/方法直接在definProperty()中使用对象字面量定义即可

读取属性:
使用ES5中的Object.getOwnPropertyDescriptor()方法获取给定属性,接收两个参数:属性所在的对象、读取其描述符的属性名词

创建对象:

1.工厂模式
最基本的模式,抽象了创建具体对象的过程,也就是把具体的变量都抽出来,把写死的东西变成可替换的

function wapper(name, age){
	var o = new Object();
	o.name = name;
	o.age = age;
	o.sayName = function(){
		console.log(this.name);
	}
    return o;
}
var person1 = new wapper(“abc”, 12);
person1.sayName();                        //abc

2.构造函数模式
构造函数模式也是最为基本的一种模式,构造函数和普通的函数基本没有区别,主要的区别在于构造函数按照惯例开头首字母大写,创建该函数的实例时必须使用new操作符
所有被实例化出来的新对象,都存在一个叫做constructor的属性,该属性指向创建该对象的构造函数(这里的这个属性也存在于下面的原型中
检测对象的构造函数也可以使用原本的instanceof操作符
缺点:
这种方法创建的函数虽然具备相当的封装性,但是每次创建一个新对象时,都会去重新实例化一遍该构造函数,也就是说每一个对象身上都携带着属于自己的那个实例化完成的函数,各个对象之间并不互通。当构造函数内部存在方法时,两个同样实例化该构造函数的对象方法会分别创建一次该方法,造成资源浪费

function Person(name, age){
   this.name = name;
   this.age = age;
   this.sayName = function(){
   	  console.log(this.name);
	}
}
var person1 = new Person(“abc”, 12);
person1.sayName();                      //abc

3.原型模式
每一个函数都存在一个属性叫做“原型(prototype)”这个属性是一个指针,指向一个对象。这里的原型可以理解为函数是依据原型创建出来的,类似于其他语言中父类的概念
每一个原型身上都存在一个属性constructor,这个属性指向原型的构造函数(这个属性在上文中也存在)
这里存在一个特殊的属性 __ proto __ ,该属性指向构造函数的原型。这个属性特殊的原因是虽然存在,但是正常状态下我们只是将其作为一种参考,并不会进行调用。该属性个人没有测试,但是这本书上写的是只有在chrome、firefox、safari中存在。
这里存在一个确定对象和构造函数之间确定是否存在原型的方法 构造函数.isPrototypeOf(对象);

原型在被创建之初只存在constructor属性,后面添加属性/方法只能使用 构造函数.prototype.属性/方法 的方式进行创建,引用时直接使用对象.原型属性/方法即可,但是这种引用方法不可以直接进行修改属性/方法,直接使用 对象.属性/方法 = value 的方法进行修改,修改的并不是原型中的属性/方法,而是在构造函数中进行修改,也就是在构造函数中新建了这个属性/方法,覆盖掉从原型中得到的同名属性/方法。直接在构造函数中写出与原型中同名的属性/方法结果同上。

使用 hasOwnProperty() 方法可以判断一个属性/方法是存在于原型中还是存在于构造函数/对象中。
使用 Object.keys() 方法可以取得对象上所有可枚举的实例属性/方法,使用 Object.getOwnPropertyNames() 方法可以获取对象上所有的方法。这里需要注意的是 constructor属性 不是可枚举的。
原型
这里实例化函数出现的对象可以直接使用原型上的方法和属性,但是这种使用和构造函数关系并不大,只是对象在实例化构造函数的同时直接拿到了属于该构造函数的原型。

function wapper(){ 
};
wapper.prototype.name = “abc”;
wapper.prototype.age = 12
wapper.prototype.sayName = function(){
	console.log(this.name);
}
var person1 = new wapper();
person1.sayName();                                //abc
var person2 = new wapper();
console.log(person1.sayName == person2.sayName);  //true

使用原本的那种原型方法定义属性/方法重复的代码比较多,所以另一种创建原型属性/方法是使用对象字面量的方式,但是这种创建方式在本质上是重写了该构造函数的prototype,和原有的prototype斩断了关系,也正是因为如此,新创建出来的原型身上的constructor属性指向Object,并不指向该构造函数所以在后面原型以对象字面量方式书写时,必须指定constructor的值为该构造函数。

这里需要注意的是,这种方法人工定义出来的constructor属性是可枚举的。
原型是动态的,而且在原型上显示出的属性/方法都是可以实时显示在对象实例中的

这里自己使用对象字面量创建的原型相当于将构造函数原本的原型挤掉,但是这个默认原型仍然存在,而在对象字面量创建原型之间实例化的对象并不指向新建的原型,而是仍然指向默认原型,所以在使用对象字面量创建原型时,千万注意实例化对象要将位置放在创建原型之后。
原型字面量
其实这种原型的模式并非是后来创建的,而是早在JavaScript创建之初就存在的,所有Js原生的引用类型也是用此方法创建的。比如:Array中的sort()方法,其实并非是定义在Array构造函数中的,而是定义在Array.prototype上的。

缺点:
原型模式虽然形成了一种链式结构,但是不可避免的一种弊端就是原型身上的属性/方法是公用的,原型模式被创建出来的核心就是他的共享性,而最大的弊端也是共享性。

当一个对象改变原型中的某些属性时,对象会直接修改原型中的某些属性(这种比较典型的就是数组(Array)结构);但是有些属性会直接在对象中创建一个该属性的copy版本,修改这个copy版本,对原型不修改(这种比较典型的就多一些,比如String、Number等)。

当一个对象改变原型中的方法时,对象并不去修改原型中的方法,而是类似于上面的String一样,在对象中创建一个方法的copy版本,修改这个copy版本,这时候对象的内部存在了该方法,就将原型中的该方法覆盖掉,返回值也是修改后的,但是当第二个同样基于该构造函数实例化的对象再次调用该方法时,仍然拿到的是原型中的那个方法。

function Person(){
}
person.prototype = {
    constructor: Person,
    name: “abc”,
    age: 12,
    colors: [“blue”, “yellow”, “red”],
    sayName: function(){
    	console.log(this.name);
	}
}
var person1 = new Person();
person1.name = “def”;
person1.age = 14;
person1.colors.push(“black”);
person1.sayName = function(){
   this.age;
};

var person2 = new Person();
console.log(person1.name+”,”+person2.name);         //def, abc
console.log(person1.age+”,”+person2.age);           //12, 14
console.log(person1.colors+”,”+person2.colors);     //blue, yellow, red,blue
console.log(person1.sayName+”,”+person2.sayName);   //this.age, this.name

4.组合使用构造函数模式和原型模式(个人觉得比较完美的模式)
组合使用构造函数模式与原型模式,构造函数模式用于定义实例属性,原型模式用于定义方法和共享的属性。
也就是说,需要每个对象单独分开的,直接在构造函数中定义出来,而所有对象需要共享的属性,直接定义在原型中

function Person(name, age){
   this.name = name;
   this.age = age;
}
person.prototype = {
   constructor: Person,
   colors: [“green”, “blue”, “black”],
   sayName: function(){
  		this.colors;
	}
}

5.动态原型模式
为了解决与其他OO语言样式有差异问题(个人觉得没必要),将所有的信息都封装在构造函数中。

function Person(name, age){
   this.name = name;
   this.age = age;
   if(typeof this.sayName != “function”){
       Person.prototype.sayName = function(){
   			console.log(this.name);
		}
	}
}

6.寄生构造函数模式
这种寄生构造函数模式其思想就是在函数的内部创建一个能够封装代码的对象,然后将封装完成的对象使用return语句抛出到函数的外部,实现一种模式。(个人不建议使用)

function Person(name, age){
   var o = new Object();
   o.name = name;
   o.age = age;
   return o;
}
var person = new Person(“abc”, 12);

7.稳妥构造函数模式
稳妥对象是指没有公共属性,其方法也不引用this值的对象(个人感觉商业项目中作用不大)

function Person(name, age){
   var o = new Object();
   o.name = name;
   o.age = age;
   return o;
}
var person = Person(“abc”, 12);

猜你喜欢

转载自blog.csdn.net/Feng_ye__/article/details/89330242