1. 属性类型
1.1 数据属性
1.1.1 定义
数据属性包含一个值的位置,在这个位置可以读取可写入值,数据属性有4个描述其行为的特性
- [[Configurable]]:表示能否通过delete删除属性,能否修改属性的特性,或者能否把属性修改为访问器属性。默认值为false
- [[Enumerable]]:表示能否通过for-in访问属性,默认值为false
- [[Writable]]:表示能否修改属性的值。默认值为false
- [[Value]]:包含属性的数据值,读取属性值的时候,从这个位置读,写入属性的时候,把新值保存在这个位置。默认值为undefined
注:所有直接定义在对象上的属性 configurable enumerable writable 三个特性的值都为true
1.1.2 修改属性
Object.defineProperty(obj, prop, descriptor)
obj:要修改的对象
prop:要定义或修改的属性的名称或 Symbol
descripor:要定义或修改的属性描述符
1.2 访问器属性
访问器属性不包含数据值,包含一对getter setter函数,在读取访问器属性时会调用getter函数,写入访问器属性时,会调用setter函数并传入新值
1.2.1 定义
- [[Configurable]]: 表示能否通过delete删除属性,能否修改属性的特性,或者能否把属性修改为访问器属性。默认值为false
- [[Enumerable]]:表示能否通过for-in访问属性,默认值为false
- [[Get]]: 获取函数,读取属性时调用,默认值为undefined
- [[Set]]: 设置函数,写入属性时调用,默认值为undefined
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
1.3 定义多个属性
Object.defineProperties(obj, props)
1.4 读取属性的特性
Object.getOwnPropertyDescriptor(obj,prop)
2. 创建对象
2.1 工厂模式
缺点:无法解决对象识别问题
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
}
2.2 构造函数模式
缺点:对象方法在每个实例上都要创建一遍,造成资源浪费。
function Person(name,age,job) {
this.name = name
this.age = age
this.job = job
this.sayName = function(){
console.log(this.name)
}
}
2.3 原型模式
缺点:1.无法通过构造函数传参。2.所有属性和方法都是共享的,对于引用类型属性值来说,修改一个对象的值会影响到其他对象。
function Person() {
Person.prototype.name = 'Nicholas'
Person.prototype.age = 29
Person.job = 'softWare Engineer'
Person.prototype.sayName = function(){
console.log(this.name)
}
}
更简洁的原型模式
function Person(){}
Person.prototype = {
name: 'Nicholas',
age: 29,
job: 'softWare Engineer',
sayName: function(){
console.log(this.name)
}
}
Object.defineProperty(Person.prototype,'constructor',{
enumerable: false,
value: Person
})
2.4 组合构造函数和原型模式
构造函数模式定义实例属性,原型模式用于定义方法和共享属性,每个实例都会有自己的一份实例属性副本,同时又共享这对方法的引用,最大限度地节省了内存。
function Person(name, age, job) {
this.name = name
this.age = age
this.job = job
}
Person.prototype = {
sayName: function() {
console.log(this.name)
}
}
Object.defineProperty(Person.prototype,'constructor',{
enumerable: false,
value: Person
})
2.5 寄生构造模式
使用场景:创建一个具有额外方法的数组,不能直接修改Array构造函数
function SpecialArray() {
var values = []
[].push.apply(values,arguments)
values.toPipedString = function() {
return this.join("|")
}
return values
}
3. 继承
3.1 借用构造函数(经典继承)
缺点:构造函数中定义的方法无法实现复用,超类型原型中定义的方法,对于子类型不可见。
function SuperType(name) {
this.name = name
this.colors = ['red','blue','green']
}
function SubType() {
SuperType.call(this,'Nicholas')
}
3.2 组合继承(伪经典继承)
缺点:调用两次超类构造函数,一次在创建子类型原型时,另一次在子类型构造函数内部
function SuperType(name) {
this.name = name
this.colors = ['red','blue','green']
}
SuperType.prototype.sayName = function() {
console.log(this.name)
}
function SubType(name,age) {
SuperType.call(this,name)
this.age = age
}
SubType.prototype = new SuperType()
SubType.prototype.constructor = SubType
SubType.prototype.sayAge = function() {
console.log(this.age)
}
3.3 原型式继承
没有使用严格意义上的构造函数,借助原型可以基于已有对象创建新对象,同时不必因此创建自定义类型。
function object(o) {
function F(){}
F.prototype = o
return new F()
}
ES5通过新增Object.create方法规范化了原型式继承。该方法接受两个参数,一个是用作新对象原型的对象,另一个是为新对象定义额外属性的描述对象(可选)
var person = {
name: 'Nicholas',
friends: ['Shelby','Court','Van']
}
var anotherPerson = Object.create(person,{
name: {
value: 'Greg'
}
})
3.4 寄生组合式继承
通过构造函数继承属性,通过原型链的混成形式继承方法,基本思路是:不必为了指定子类型的原型调用超类型的构造函数,本质上就是使用寄生式继承来继承超类型原型,然后将返回对象指定给子类型。
//构造以超类型原型为原型的空对象作为子类型的原型,避免了重复调用超类型构造函数
function inheritPrototype(subType,superType) {
var prototype = Object.create(superType.prototype)
prototype.constructor = subType
return prototype
}
function SuperType(name) {
this.name = name
this.colors = ['red','blue','green']
}
SuperType.prototype.sayName = function() {
console.log(this.name)
}
function SubType(name, age) {
SuperType.call(this,name)
this.age = age
}
SubType.prototype = inheritPrototype(SubType,SuperType)
SubType.prototype.sayAge = function() {
console.log(this.age)
}
4. 对象复制
4.1 定义
对象复制分为深复制和浅复制,如果对象中属性的值是引用类型,浅复制只是复制了堆内存的引用地址,通常在业务需求中出现的浅复制是指复制引用对象的第一层,也就是,基本类型复制新值,引用类型复制引用地址。深复制基本类型复制新值,引用类型开辟新的堆内存。
4.2 深复制和浅复制JS内置的实现
深复制:JSON.parse( JSON.stringify(someobj))
浅复制:Object.assign( { } ,myobject )