1. 属性类型

ECMAScript有两种属性,数据属性和访问器属性。

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
  1. var book = {
  2. _year: 2004,
  3. edition:1
  4. }
  5. Object.defineProperty(book,"year",{
  6. get: function(){
  7. return this._year
  8. },
  9. set: function(newValue){
  10. if(newValue > 2004) {
  11. this._year = newValue
  12. this.edition += newValue - 2004
  13. }
  14. }
  15. })
  16. book.year = 2005
  17. console.log(book.edition) //2

1.3 定义多个属性

Object.defineProperties(obj, props)

1.4 读取属性的特性

Object.getOwnPropertyDescriptor(obj,prop)

2. 创建对象

2.1 工厂模式

缺点:无法解决对象识别问题

  1. function createPerson(name,age,job) {
  2. var o = new Object()
  3. o.name = name
  4. o.age = age
  5. o.job = job
  6. o.sayName = function() {
  7. console.log(this.name)
  8. }
  9. return o
  10. }

2.2 构造函数模式

缺点:对象方法在每个实例上都要创建一遍,造成资源浪费。

  1. function Person(name,age,job) {
  2. this.name = name
  3. this.age = age
  4. this.job = job
  5. this.sayName = function(){
  6. console.log(this.name)
  7. }
  8. }

2.3 原型模式

缺点:1.无法通过构造函数传参。2.所有属性和方法都是共享的,对于引用类型属性值来说,修改一个对象的值会影响到其他对象。

  1. function Person() {
  2. Person.prototype.name = 'Nicholas'
  3. Person.prototype.age = 29
  4. Person.job = 'softWare Engineer'
  5. Person.prototype.sayName = function(){
  6. console.log(this.name)
  7. }
  8. }

更简洁的原型模式

  1. function Person(){}
  2. Person.prototype = {
  3. name: 'Nicholas',
  4. age: 29,
  5. job: 'softWare Engineer',
  6. sayName: function(){
  7. console.log(this.name)
  8. }
  9. }
  10. Object.defineProperty(Person.prototype,'constructor',{
  11. enumerable: false,
  12. value: Person
  13. })

2.4 组合构造函数和原型模式

构造函数模式定义实例属性,原型模式用于定义方法和共享属性,每个实例都会有自己的一份实例属性副本,同时又共享这对方法的引用,最大限度地节省了内存。

  1. function Person(name, age, job) {
  2. this.name = name
  3. this.age = age
  4. this.job = job
  5. }
  6. Person.prototype = {
  7. sayName: function() {
  8. console.log(this.name)
  9. }
  10. }
  11. Object.defineProperty(Person.prototype,'constructor',{
  12. enumerable: false,
  13. value: Person
  14. })

2.5 寄生构造模式

使用场景:创建一个具有额外方法的数组,不能直接修改Array构造函数

  1. function SpecialArray() {
  2. var values = []
  3. [].push.apply(values,arguments)
  4. values.toPipedString = function() {
  5. return this.join("|")
  6. }
  7. return values
  8. }

3. 继承

3.1 借用构造函数(经典继承)

缺点:构造函数中定义的方法无法实现复用,超类型原型中定义的方法,对于子类型不可见。

  1. function SuperType(name) {
  2. this.name = name
  3. this.colors = ['red','blue','green']
  4. }
  5. function SubType() {
  6. SuperType.call(this,'Nicholas')
  7. }

3.2 组合继承(伪经典继承)

缺点:调用两次超类构造函数,一次在创建子类型原型时,另一次在子类型构造函数内部

  1. function SuperType(name) {
  2. this.name = name
  3. this.colors = ['red','blue','green']
  4. }
  5. SuperType.prototype.sayName = function() {
  6. console.log(this.name)
  7. }
  8. function SubType(name,age) {
  9. SuperType.call(this,name)
  10. this.age = age
  11. }
  12. SubType.prototype = new SuperType()
  13. SubType.prototype.constructor = SubType
  14. SubType.prototype.sayAge = function() {
  15. console.log(this.age)
  16. }

3.3 原型式继承

没有使用严格意义上的构造函数,借助原型可以基于已有对象创建新对象,同时不必因此创建自定义类型。

  1. function object(o) {
  2. function F(){}
  3. F.prototype = o
  4. return new F()
  5. }

ES5通过新增Object.create方法规范化了原型式继承。该方法接受两个参数,一个是用作新对象原型的对象,另一个是为新对象定义额外属性的描述对象(可选)

  1. var person = {
  2. name: 'Nicholas',
  3. friends: ['Shelby','Court','Van']
  4. }
  5. var anotherPerson = Object.create(person,{
  6. name: {
  7. value: 'Greg'
  8. }
  9. })

3.4 寄生组合式继承

通过构造函数继承属性,通过原型链的混成形式继承方法,基本思路是:不必为了指定子类型的原型调用超类型的构造函数,本质上就是使用寄生式继承来继承超类型原型,然后将返回对象指定给子类型。

  1. //构造以超类型原型为原型的空对象作为子类型的原型,避免了重复调用超类型构造函数
  2. function inheritPrototype(subType,superType) {
  3. var prototype = Object.create(superType.prototype)
  4. prototype.constructor = subType
  5. return prototype
  6. }
  7. function SuperType(name) {
  8. this.name = name
  9. this.colors = ['red','blue','green']
  10. }
  11. SuperType.prototype.sayName = function() {
  12. console.log(this.name)
  13. }
  14. function SubType(name, age) {
  15. SuperType.call(this,name)
  16. this.age = age
  17. }
  18. SubType.prototype = inheritPrototype(SubType,SuperType)
  19. SubType.prototype.sayAge = function() {
  20. console.log(this.age)
  21. }

4. 对象复制

4.1 定义

对象复制分为深复制和浅复制,如果对象中属性的值是引用类型,浅复制只是复制了堆内存的引用地址,通常在业务需求中出现的浅复制是指复制引用对象的第一层,也就是,基本类型复制新值,引用类型复制引用地址。深复制基本类型复制新值,引用类型开辟新的堆内存。

4.2 深复制和浅复制JS内置的实现

深复制:JSON.parse( JSON.stringify(someobj))
浅复制:Object.assign( { } ,myobject )