《JS高程》对象&原型学习笔记

时间:2022-01-07 05:41:05
ECMA-262 把对象定义为:”无序属性的集合,其属性可以包含基本值、对象或者函数。”可以把 ECMAScript 的对象想象成散列表:无非就是一组名值对,其中值可以是数据或函数。
6.1.理解对象
创建自定义对象的两种方式:
(1)创建一个 Object 的实例:  var person = new Object(); 
(2)对象字面量(首选):  var person = {};  
---------------------------------------------------------------------------------------------
ECMAScript 中有两种属性:数据属性和访问器属性。
数据属性包含一个数据值的位置。
    要修改属性默认的特性,必须使用 ECMAScript 5 的 Object.defineProperty() 方法。
访问器属性不包含数据值,它们包含一对 getter 和 setter 函数。
    访问器属性不能直接定义,必须使用 Object.defineProperty() 来定义。
    __year:year 前面的下划线是一种常用的标记,表示只能通过对象方法访问。
----------------------------------------------------------------------------------------------
定义多个属性:Object.defineProperties() 方法,可同时定义数据属性和访问器属性。
----------------------------------------------------------------------------------------------
读取属性的特性:Object.getOwnPropertyDescriptor() 方法,可以取得给定属性的描述符。
6.2.创建对象
6.2.2 构造函数模式
构造函数与其他函数的唯一区别,就在于调用它们的方式不同。任何函数,只要通过 new 操作符来调用,那它就可以作为构造函数;而任何函数,如果不通过 new 操作符来调用,那它就跟普通函数也不会有什么两样。
// 当作构造函数使用
var person = new Person("Nick", 29, "Software Engineer");
person.sayName(); //"Nick" // 作为普通函数调用
Person("Greg", 27, "Doctor");
window.sayName(); //"Greg" //在另一个对象的作用域中调用
var o = new Object();
Person.call(o, "Kristen", 25, "Nurse");
o.sayName(); //"Kristen"

构造函数的主要问题:每个方法都要在实例上重新创建一遍,因为 ECMAScript 中的函数是对象,因此每定义一个函数,也就实例化了一个对象。

6.2.3 原型模式
我们创建的每个函数都有一个 prototype(原型)属性,该属性是一个指针,指向一个对象。
使用原型对象的好处是可以让所有对象实例共享它所包含的属性和方法。
(1)理解原型对象
    无论什么时候,只要创建了一个新函数,就会根据一组特定的规则为该函数创建一个 prototype 属性,这个属性指向函数的原型对象。在默认情况下,所有原型对象都会自动获得一个 constructor (构造函数)属性,该属性包含指向 prototype 属性所在函数的指针。
    创建了自定义的构造函数之后,其原型对象默认只会取得 constructor 属性;至于其他方法,则都是从 Object 继承而来的。当调用构造函数创建一个新实例后,该实例的内部将包含一个指针([[Prototype]],内部属性),指向构造函数的原型对象。
《JS高程》对象&原型学习笔记
在所有实现中都无法访问到[[Prototype]],可以通过 isPrototypeOf() 方法来确定对象之间是否存在实例和原型的关系:
alert(Person.prototype.isPrototypeOf(person1)); //true
alert(Person.prototype.isPrototypeOf(person2)); //true
ECMAScript 5 新增了一个新方法,叫 Object.getPrototypeOf(),返回 [[Prototype]] 的值:
alert(Object.getPrototypeOf(person1) === Person.prototype);    //true
alert(Object.getPrototypeOf(person1).name); //"Nicholas"
使用 hasOwnProperty() 方法可以检测一个属性是存在于实例中,还是存在于原型中。这个方法(不要忘了它是从 Object 继承来的)只在给定属性存在于对象实例中时,才会返回 true。通过该属性,什么时候访问的是实例属性,什么时候访问的是原型属性就一清二楚了。
var person1 = new Person();
var person2 = new Person(); alert(person1.hasOwnProperty("name")); //false person1.name = "Greg";
alert(person1.name); //"Greg"——来自实例
alert(person1.hasOwnProperty("name")); //true alert(person2.name); //"Nicholas"——来自原型
alert(person2.hasOwnProperty("name")); //false delete person1.name;
alert(person1.name); //"Nicholas"——来自原型
alert(person1.hasOwnProperty("name")); //false

ECMAScript 5 的 Object.getOwnPropertyDescriptor() 方法只能用于实例属性,要取得原型属性的描述符,必须直接在原型对象上调用 Object.getOwnPropertyDescriptor() 方法。

(2)原型与 in 操作符
有两种方式使用 in 操作符:单独使用和在 for-in 循环中使用。在单独使用时,in 操作符会在通过对象访问给定属性时返回 true,无论该属性存在于实例中还是原型中。
alert(person1.hasOwnProperty("name"));    //false;
alert("name" in person1); //true person1.name = "Greg";
alert(person1.name); //"Greg"——来自实例
alert(person1.hasOwnProperty("name")); //true
alert("name" in person1); //true; alert(person2.name); //"Nicholas"——来自原型
alert(person2.hasOwnProperty("name")); //false
alert("name" in person2); //true; delete person1.name;
alert(person1.name); //"Nicholas"——来自原型
alert(person1.hasOwnProperty("name")); //false
alert("name" in person1); //true;
同时使用 hasOwnProperty() 方法和 in 操作符,就可以确定该属性到底是存在于对象中,还是存在于原型中。
function hasPrototypeProperty ( object, name ) {
return !object.hasOwnProperty ( name ) && ( name in object );
}
在使用 for-in 循环时,返回的是所有能够通过对象访问的、可枚举的(enumerated)属性,其中既包括存在于实例中的属性,也包括存在于原型中的属性。屏蔽了原型中不可枚举属性(即将 [[Enumerable]] 标记为 false 的属性)的实例属性也会在 for-in 循环中返回,因为根据规定,所有开发人员定义的属性都是可枚举的。
不可枚举的属性和方法:hasOwnProperty()、propertyIsEnumerable()、toLocaleString()、toString()、valueOf()、constructor、prototype
取得对象上所有可枚举的实例属性,可以使用 ECMAScript 5 的 Object.keys() 方法。
如果想要得到所有实例属性,无论它是否可枚举,都可以使用 Object.getOwnPropertyNames() 方法。
Object.keys() 和 Object.getOwnPropertyNames() 方法都可以用来替代 for-in 循环。
(3)更简单的原型语法
function Person(){
} Person.prototype = {
constructor : Person,
name : "Nicholas",
age : 29,
job : "Software Engineer",
sayName : function () {
alert(this.name);
}
};

注意:以这种方式重设 constructor 属性会导致它的 [[Enumerable]] 特性被设置为 true。默认情况下,原生的 constructor 属性是不可枚举的。

6.2.4 组合使用构造函数模式和原型模式
    创建自定义类型的最常见方式,就是组合使用构造函数模式与原型模式。构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性。另外,这种混成模式还支持向构造函数传递参数;可谓是集两种模式之长。
 function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.friends = ["Shelby", "Court"];
} Person.prototype = {
constructor : Person,
sayName : function() {
alert(this.name);
}
} var person1 = new Person("Nicholas", 29, "Software Enginner");
var person2 = new Person("Greg", 27, "Doctor"); person1.friends.push("Van");
alert(person1.friends); //"Shelby, Court, Van"
alert(person2.friends); //"Shelby, Court"
alert(person1.friends === person2.friends); //false
alert(person1.sayName === person2.sayName); //true

参考文章:深入解读JavaScript面向对象编程实践

JS核心系列:浅谈 原型对象和原型链

A Plain English Guide to JavaScript Prototypes