JavaScript高级程序设计第3版总结p156
1.JavaScript中的对象
首先,ECMAScript 中函数实际上是对象。每个函数都是 Function 类型的实例,而且都与其他引用类型一样具有属性和方法。如此,根据ECMA-262 中对象的定义:“无序属性的集合,其属性可以包含基本值、对象或者函数。”,我们可以把 ECMAScript 的对象想象成散列表,一组名值对。
创建自定义对象实例的方法有两种:一种是var person = new Object()
再为其添加属性和方法person.name="Yann LeCun"
;另一种是通过对象字面量的方法var person = {name:"Yann LeCun",age:56}
,这里name就是对象实例person中的一个属性。
定义了对象的属性后,有时候还需要设置对象属性的属性,譬如前述对象实例person的name属性是否可修改,若可修改,修改name属性时是否需要同时更新及如何更新age属性。
ECMAScript 中有两种属性描述了对象属性(property)的各种特征:数据属性和访问器属性。(特性是内部值,ECMA-262规范把它们放在了两对儿方括号中)
-
数据属性:配置对象属性属性的一些特征
- [[Configurable]] :表示能否通过 delete 删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性,默认值为 true。
- [[Enumerable]] :表示能否通过 for-in 循环返回属性,默认值为 true。
- [[Writable]] :表示能否修改属性的值,默认值为 true。
- [[Value]] :包含这个属性的数据值,默认值为 undefined。
要修改属性默认的特性,必须使用 ECMAScript 5 的 **Object.defineProperty() **方法,三个参数:属性所在的对象、属性的名字和一个描述符对象(configurable 、 enumerable 、 writable 和 value)。
Object.defineProperty(person, "name", { writable: false, value: "Nicholas" });
注意:一旦configurable 设置为 false,再调用Object.defineProperty() 方法修改除 writable 之外的特性,都会导致错误。
-
访问器属性:包含一对儿 getter 和 setter 函数。访问器属性有如下 4 个特性:
- [[Configurable]] :同数据属性。
- [[Enumerable]]:同数据属性。
- [[Get]] :在读取属性时调用的函数,返回属性值。默认值为 undefined 。
- [[Set]] :在写入属性时调用的函数,传入新值。默认值为 undefined 。
访问器属性不能直接定义,必须使用 Object.defineProperty() 来定义。
var book = { _year: 2004, edition: 1 }; Object.defineProperty(book, "year", { get: function(){ return this._year; }, set: function(newValue){ if (newValue > 2004) { this._year = newValue; this.edition += newValue - 2004; } } }); book.year = 2005; console.log(book.edition);//2
-
Object.defineProperties(obj,{})同时定义多个属性,包括数据属性和访问器属性。
var book = {};
Object.defineProperties(book, {
_year: {
value: 2004
},
edition: {
value: 1
},
year: {
get: function(){
return this._year;
},
set: function(newValue){
if (newValue > 2004) {
this._year = newValue;
this.edition += newValue - 2004;
}
}
}
});
- Object.getOwnPropertyDescriptor(book, “_year”)取得给定属性的描述符
var descriptor = Object.getOwnPropertyDescriptor(book, "_year");
console.log(descriptor.value);//2004
alert(descriptor.configurable); //false
2.如何创建对象
可以通过var obj = {key:value}
对象字面量的方式来创建对象,这种方式简便直接,但若需要重复创建多个对象时就会很冗余(每个都要写一遍)。JS中创建对象有多种不同的模式,最基础的应该是工厂模式、构造函数模式和原型模式,基于这三种模式还衍生出了其他的如,原型构造函数组合模式、动态原型模式、寄生构造函数模式、稳妥构造函数模式,涉及到模式,更像是去理解一种设计思想,具有一定的抽象性。
- 工厂模式
function createBook(name,year,edition){
var obj = new Object();
obj.name = name;
obj.year = year;
obj.edition = edition;
obj.sayName = function(){
console.log(this.name)
}
return obj;
}
var book1 = createBook('Master Javascript',2016,2);
var book2 = createBook('Master CSS',2016,2);
传入不同的参数返回不同的实例;单纯的工厂模式创建对象存在的主要问题是,无法进行对象识别,也就是不能自己创建类型。像上例中,book1
和book2
是同一个类型,但是instanceof
只能确定他们是object
,而没有创建它们具体属于的类。这种感觉就像是说二哈和泰迪是动物,其实更具体来说他们是狗,但若没有狗这个类,那只能说他们是动物了。
- 构造函数模式
function Book(name,year,edition){
this.name = name;
this.year = year;
this.edition = edition;
this.sayName = function(){
console.log(this.name)
}
}
var book1 = new Book('Master Javascript',2016,2);
var book2 = new Book('Master CSS',2016,2);
console.log(book1 instanceof Book);//true
console.log(book1 instanceof Object);//true
1.首先看下this
,javascript
中的this引用的是函数据以执行的环境对象.通过new
来调用构造函数,将构造函数的作用域赋给新对象,所以构造函数的this
就是指new
出来的新对象,也就将传入的参数绑定到了新创建的对象上。
2.构造函数,函数名以大写字母开头(惯例)。
3.通过构造函数创建的对象可以确定具体类型,book1
就是Book
类型。
4.构造函数与普通函数的唯一不同,就是调用方式不同,调用方式决定的是函数的作用域,任何函数通过new
来调用也就都变成了构造函数。
5.上面的构造函数中定义了一个sayName
方法,在JavaScript
中,function
是Function
类型的对象,所以上面的sayName
方法还可写为this.sayName = new Function("alert(this.name)");
,这样,每调用一次构造函数创建一个Book
类型的实例,都会给每个实例开辟一片独立的内存空间创建一个sayName
方法对象,而sayName
完全可以定义成Book
类型所有实例共享的方法,为每个实例创建一个同样的方法是奢侈浪费低性能的,这也正是构造函数模式存在问题,就是每个方法都要在每个
实例上重新创建一遍。 属于同一个类型的不同实例,不就应该既有一部分共享的,又有一部分个性化的属性和方法吗?
-
原型模式
每一个函数都有一个prototype
的属性,这个属性是一个指向函数原型对象的指针。这个原型对象中包含了某个特定类型的所有实例共享的属性和方法。function Book(){ } Book.prototype.name = 'Master JavaScript'; Book.prototype.year = 2019; Book.prototype.edition = 2; Book.prototype.sayName = function(){ console.log(this.name); } var book1 = new Book(); var book2 = new Book(); book2.name = 'Master CSS'; book2.year = 2019; book2.edition = 2; book2.sayName();//'Master CSS'; book2.author = 'CSS Author'; Book.prototype.isPrototypeOf(book1);//true Object.getPrototypeOf(book1) == Book.prototype delete book2.name; book2.name;//Master JavaScript book1.hasOwnProperty('name');//false; book2.hasOwnProperty('name');//true; ’name' in book1;//true //判断属性是存在于原型还是实例中 function hasPrototypeProperty(object, name){ return !object.hasOwnProperty(name) && (name in object); } Object.keys(book1);//[] Object.keys(book2);//["name", "year", "eidtion", "author"] Object.getOwnPropertyNames(Book.prototype);// ["constructor", "name", "year", "edition", "sayName"]
1.JavaScript中,创建一个函数就会根据一组特定的规则为该函数创建一个
prototype
属性,指向函数的原型对象,语言如此设计,基于此实现对象创建继承等。刚创建函数时,函数的原型对象中都会自动包含prototype
属性所在函数的指针Book.prototype.constructor
.
2.调用自定义的构造函数创建一个实例时,该实例内部包含一个指针[[prototype]]
,chrome等中每个实例都有一个__proto__
属性,指向生成该实例的构造函数的原型对象,与构造函数已没有关系。
3.isPrototypeOf()
,判断某个构造函数的原型是不是某个实例的原型。Object.getPrototypeOf()
,获取某个实例的__prototype
所指的对象。
4.访问实例属性时,先访问定义在实例上的属性,没有再去原型上搜索。在实例上新增的与原型中同名的属性,会屏蔽原型中定义的属性。使用 delete 操作符则可以完全删除实例属性,从而让我们能够重新访问原型中的属性
5.hasOwnProperty()
,从Object
中继承来的方法,当属性是在实例中定义的属性时,才会返回true
。
6.in
,in 操作符会在通过对象能够访问给定属性时返回 true。
7. 要取得对象上所有可枚举的实例属性,可以使用 ECMAScript 5 的Object.keys()
方法。
8.Object.getOwnPropertyNames()
,得到所有实例属性,无论它是否可枚举。
9. 通过对象字面量方法创建原型对象会覆盖掉原型中constructo
属性。手动创建constructor
时,会在会导致它的 [[Enumerable]] 特性被设置为 true,可借助前述defineProperty
设置其特性。
10.先创建实例后通过对象字面量定义原型对象时,会出错,因为会重写原型对象,导致实例中的[[prototype]]
属性无法指向创建的原型对象。
11.原型对象存在问题,是由其共享的属性导致的,当原型属性中包含引用类型时,实例之间会相互影响。 -
构造函数和原型组合使用
既然构造函数过于’个性‘,原型过于‘共享’,如果把同一类型实例共享的属性用原型定义,个性化的属性用构造函数来定义,问题不就解决了吗?构造函数与原型混成的模式,是目前在 ECMAScript 中使用最广泛、认同度最高的一种创建自定义类型的方法。function Book(name,year,edition){ this.name = name; this.year = year; this.edition = edition; this.category = ['computer','software']; } Book.prototype = { consturctor:Book, sayName:function(){ console.log(this.name); } } var book1 = new Book('Master JavaScript',2019,2); var book2 = new Book('Master CSS',2020,2); book1.category.push('JavaScript');//["computer", "software", "JavaScript"] book1.sayName == book2.sayName;//true book2.category;//["computer", "software"]
-
动态原型模式
根据需要,在构造函数中根据条件动态的初始化原型。function Book(name,year,edition){ this.name = name; this.year = year; this.edition = edition; if(typeof this.sayName != "function"){ Book.prototype.sayName = function(){ console.log(this.name); } } }
动态原型模式只通过构造函数来创建实例,更接近其他OO(面向对象)的语言,便于理解。
-
寄生构造函数模式
实现方式是,创建一个函数,该函数的作用仅仅是封装创建对象的代码,然后再返回新创建的对象;function Book(name,year,edition){ var obj = new Object(); obj.name = name; obj.year = year; obj.edition = edition; obj.sayName = function(){ console.log(this.name); } return obj; } book1 = new Book('Master JavaScript',2019,2); book1 instanceof Book;//false
寄生构造函数和工厂函数一样,区别在与调用方式,这里加了调用
new
操作符,Book函数的作用域赋值给了new出来的对象,通过返回值重写了这个new出来的对象,所以无法通过instanceof
来识别对象。这个模式可以在特殊的情况下用来为对象创建构造函数。如特殊的数组。寄生可以理解为寄生在内部创建对象的类型上。 -
稳妥构造函数
稳妥对象,没有公共属性,不使用this
和new
。function Book(name,year,edition){ var obj = {}; obj.sayName:function(){ console.log(name); } return obj; } var book = Book('Master CSS',2019,2); book.sayName();//'Master CSS'
实现数据成员的私有,只能通过对象开放的方法访问其中的数据属性,安全性高。
其实,这两部分概括来说是,一部分是JavaScript
对象属性相关的特性,一部分是创建对象的工厂函数、构造函数和原型方法,有时间补上几张图,条分缕析方便理解,加深印象。