封裝、構造函數的繼承、非構造函數的繼承

繼承和原型鏈

(一)封裝

  • Javascript是一種基於對象(object-based)的語言,你遇到的所有東西幾乎都是對象。
  • 但是,它又不是一種真正的面向對象編程(OOP)語言,因爲它的語法中沒有class(類)。

1.1. 生成實例對象的原始模式

 var Cat = {
   name: '',
   color: ''
 }
 // 現在,我們需要根據這個原型對象的規格(schema),生成兩個實例對象。
 var cat1 = {} // 創建一個空對象
 cat1.name = '大毛' // 按照原型對象的屬性賦值
 cat1.color = '黃色'
// var cat2 = {}
// cat2.name = '二毛'
// cat2.color = '黑色'

1.2. 原始模式的改進

function Cat(name, color) {
  return {
    name: name,
    color: color
  }
}
var cat1 = Cat('大毛', '黃色')
var cat2 = Cat('二毛', '黑色')
這種方法的問題依然是,cat1和cat2之間沒有內在的聯繫,不能反映出它們是同一個原型對象的實例

1.3. 構造函數模式

  • 謂"構造函數",其實就是一個普通函數,但是內部使用了this變量。對構造函數使用new運算符,就能生成實例,並且this變量會綁定在實例對象上。
 function Cat(name, color) {
   this.name = name

   this.color = color
 }
 var cat1 = new Cat('大毛', '黃色')
 var cat2 = new Cat('二毛', '黑色')
 console.log(cat1.name) // 大毛
 console.log(cat1.color) // 黃色
 // console.log('cat1', cat1)
 // console.log('cat2', cat2)
 // 這時cat1和cat2會自動含有一個constructor屬性,指向它們的構造函數。
 // console.log(cat1.constructor === Cat)
 // console.log(cat2.constructor === Cat)
 console.log(cat1 instanceof Cat)
 console.log(cat1 instanceof Cat.prototype.constructor)
 console.log(' Cat.prototype.constructor', Cat.prototype.constructor)
 console.log(' Cat.prototype.constructor ==', Cat.prototype.constructor === Cat)
 console.log(' Cat.prototype.constructor ojb', Cat.prototype.constructor === Object)
 console.log(' Cat.prototype.constructor instanceof ojb', Cat.prototype.constructor instanceof Object)

1.4.構造函數模式的問題

 function Cat(name, color) {
   this.name = name
   this.color = color
   this.type = '貓科動物'
   this.eat = function() { console.log('喫老鼠') }
 }
 var cat1 = new Cat('大毛', '黃色')
 var cat2 = new Cat('二毛', '黑色')
 console.log(cat1.type) // 貓科動物
 cat1.eat() // 喫老鼠
 console.log(cat1.eat === cat2.eat) // false
 面上好像沒什麼問題,但是實際上這樣做,有一個很大的弊端。
 那就是對於每一個實例對象,type屬性和eat()方法都是一模一樣的內容,每一次生成一個實例,
 都必須爲重複的內容,多佔用一些內存。這樣既不環保,也缺乏效率。

1.5.Prototype模式

  • Javascript規定,每一個構造函數都有一個prototype屬性,
  • 指向另一個對象。這個對象的所有屬性和方法,都會被構造函數的實例繼承。
 function Cat(name, color) {
   this.name = name
   this.color = color
 }
 Cat.prototype.type = '貓科動物'
 Cat.prototype.eat = function() { console.log('喫老鼠') }
 var cat1 = new Cat('大毛', '黃色')
 var cat2 = new Cat('二毛', '黑色')
 console.log(cat1.type) // 貓科動物
 cat1.eat() // 喫老鼠
 console.log('cat1 instanceof Cat', cat1 instanceof Cat)
 console.log('Cat.prototype.isPrototypeOf(cat1)', Cat.prototype.isPrototypeOf(cat1))
console.log('Cat.prototype.isPrototypeOf(cat2)', Cat.prototype.isPrototypeOf(cat2))
 console.log('cat1.hasOwnProperty(color)', cat1.hasOwnProperty('color'))
console.log('cat1.hasOwnProperty(type)', cat1.hasOwnProperty('type'))
console.log("'color' in cat1", 'color' in cat1)

(二)構造函數實現繼承

/**
 * new實例化對象會創建一個空對象繼承構造函數的對象屬性:值 和prototype上的方法以及隱式原型上的方法
 */
function Animal() {
  this.species = '動物'
}

function Cat(name, color) {
  this.name = name

  this.color = color
}

2.1.構造函數綁定

function Cat(name, color) {
  Animal.apply(this, arguments)
  this.name = name
  this.color = color
}
var cat1 = new Cat('大毛', '黃色')
console.log(cat1.species) // 動物

2.2.prototype模式

 Cat.prototype = new Animal() // 將Cat的prototype 指向 Amimal實例
 // 相當於完全刪除了prototype 原先的值,然後賦了一個新值
 Cat.prototype.constructor = Cat
 // 任何一個prototype對象都有一個constructor屬性,指向它的構造函數
 // 每一個實例也有一個constructor屬性,默認調用protorype對象的constructor
 // 不加這一行,cat1.constructor也指向Animal!
 // 這顯然會導致繼承鏈的紊亂(cat1明明是用構造函數Cat生成的),
 // 因此我們必須手動糾正,將Cat.prototype對象的constructor值改爲Cat。這就是第二行的意思。

 var cat1 = new Cat('大毛', '黃色')
 console.log('cat1.species', cat1.species)
 console.log('Cat.prototype.constructor', Cat.prototype.constructor)
 console.log('cat1.constructor', cat1.constructor)
 console.log('Cat.prototype.constructor === cat1.constructor', Cat.prototype.constructor === cat1.constructor)

2.3.直接繼承prototype

/**

  • 第三種方法是對第二種方法的改進。由於Animal對象中,不變的屬性都可以直接寫入Animal.prototype。
  • 所以,我們也可以讓Cat()跳過 Animal(),直接繼承Animal.prototype。 */
function Animal() { }
Animal.prototype.species = '動物'
function Cat(name, color) {
  this.name = name
  this.color = color
}

Cat.prototype = Animal.prototype
Cat.prototype.constructor = Cat // 這一句實際上把Animal.prototype對象的constructor屬性也改掉了!
var cat1 = new Cat('大毛', '黃色')

console.log(cat1.species) // 動物
console.log(Animal.prototype.constructor)

2.4. 利用空對象作爲中介

/**
* 由於"直接繼承prototype"存在上述的缺點,所以就有第四種方法,利用一個空對象作爲中介
*/

/**
* 這樣做的優點是效率比較高(不用執行和建立Animal的實例了),比較省內存。
* 缺點是 Cat.prototype和Animal.prototype現在指向了同一個對象,
* 那麼任何對Cat.prototype的修改,都會反映到Animal.prototype。
*/

function Animal() { }
Animal.prototype.species = '動物'
function Cat(name, color) {
  this.name = name
  this.color = color
}
var F = function() {}
F.prototype = Animal.prototype
Cat.prototype = new F()
Cat.prototype.constructor = Cat
var cat1 = new Cat('小喵', 'white')
console.log('cat1', cat1)
console.log('cat1.species', cat1.species)
console.log('cat1.constructor', cat1.constructor)
console.log('Animal.prototype.constructor', Animal.prototype.constructor === Animal)
console.log('cat1.prototype.constructor', cat1.prototype)
console.log('F.prototype.constructor', F.prototype.constructor)
console.log('Animal.prototype.__proto__', Animal.prototype.__proto__)
console.log('Animal.prototype.__proto__.__proto__', Animal.prototype.__proto__.__proto__) // Object.prototype
console.log('Animal.prototype.__proto__.constructor', Animal.prototype.__proto__.constructor) // Object
console.log('Animal.prototype.__proto__.__proto__.constructor', Animal.prototype.__proto__.__proto__) // null

2.5.拷貝繼承

 function Animal() {}
 Animal.prototype.species = '動物'
 function Cat(name, color) {
   this.name = name
   this.color = color
 }

function extends2(Child, Parent) {
  var p = Parent.prototype
  var c = Child.prototype
  for (var i in p) {
    c[i] = p[i]
  }
  c.uber = p
}
extends2(Cat, Animal)
var cat1 = new Cat('大毛', '黃色')
console.log('cat1.species', cat1.species)

(三)非構造函數繼承

  • 兩個對象都是普通對象,不是構造函數,無法使用構造函數方法實現"繼承"。

3.1.object() 方法

/**
 * 把生成子對象的prototype屬性,指向傳入的父對象
 */
var Chinese = {
  nation: '中國'
}
 function object(o) {
   function F() {}
   F.prototype = o
   return new F()
 }
 var Doctor = object(Chinese)
 Doctor.job = '醫生'
 console.log('Doctor', Doctor)
 console.log('Doctor.nation', Doctor.nation)

3.2.淺拷貝

 /**
  * 除了使用"prototype鏈"以外,還有另一種思路:把父對象的屬性,全部拷貝給子對象,也能實現繼承。
  */
 function extendCopy(p) {
   var c = {}
   for (var i in p) {
     c[i] = p[i]
   }
   c.uber = p
   return c
 }
 var Doctor = extendCopy(Chinese)
 Doctor.career = '醫生'
 console.log(Doctor.nation) // 中國
 但是,這樣的拷貝有一個問題。那就是,如果父對象的屬性等於數組或另一個對象,
 那麼實際上,子對象獲得的只是一個內存地址,而不是真正拷貝,因此存在父對象被篡改的可能。
 extendCopy()只是拷貝基本類型的數據

3.3.深拷貝

function deepCopy(o, c) {
  // 注意點1:深度拷貝 遞歸第二個參數c是必傳的
  // 注意點2:o[i].constructor === Array  Array 是Array 對象,不是字符串'Array'
  c = c || {}
  for (var i in o) {
    if (typeof o[i] === 'object') {
      c[i] = o[i].constructor === Array ? [] : {}
      deepCopy(o[i], c[i])
    } else {
      c[i] = o[i]
    }
  }
  return c
}
Chinese.birthPlaces = ['北京', '上海', '香港']
var Doctor = deepCopy(Chinese)
console.log('Doctor', Doctor)
Doctor.birthPlaces.push('廈門')
// 在子對象上修改這個屬性,父對象不會受影響
console.log(Doctor.birthPlaces) // 北京, 上海, 香港, 廈門
console.log(Chinese.birthPlaces) // 北京, 上海, 香港
 function extend(Child, Parent) {
   var F = function() {}
   F.prototype = Parent.prototype
   Child.prototype = new F()
   Child.prototype.constructor = Child
   Child.uber = Parent.prototype // 意思是爲子對象設一個uber屬性,
   // 這個屬性直接指向父對象的prototype屬性。
   // 這一行放在這裏,只是爲了實現繼承的完備性,純屬備用性質。
 }
 extend(Cat, Animal)

 var cat1 = new Cat('大毛', '黃色')
 console.log(cat1.species) // 動物
 console.log('Animal.prototype.constructor', Animal.prototype.constructor === Animal)
 console.log('cat1.prototype.constructor', cat1.__proto__.constructor === Cat)
 console.log('Cat.prototype.constructor === Cat', Cat.prototype.constructor === Cat)
 console.log('cat1.uber', cat1)

js 定義類的三種方法

1.構造函數法

/**
 * 實例化new的過程
 * 1. 創建一個新對象
 * 2. 將構造函數中的作用域賦值給新對象( 將this指向了實例對象)
 * 3. 執行構造函數中的代碼(爲這個新的實例對象添加屬性)
 * 2. 返回這個實例對象
 */
function Cat(name, color) {
  this.name = name
  this.color = color
}
Cat.prototype.makeSound = function() {
  console.log('喵喵1~~~')
}

const cat1 = new Cat('喵1', 'white')
console.log('cat1', cat1.name)
console.log('cat1', cat1.color)
cat1.makeSound()

2. Object.create()

var Cat = {
  name: '大毛',
  makeSound: function() {
    console.log('喵喵2~~~')
  }
}
const cat2 = Object.create(Cat)
console.log(cat2.name)
cat2.makeSound()
if (!Object.create) {
  Object.create = function(o) {
    function F() {}
    F.prototype = o
    return new F()
  }
}

3.1 極簡主義法

 var Cat = {
   createNew: function() {
     var cat = {}
     cat.name = '大毛'
     cat.makeSound = function() {
       console.log('喵喵3~~~')
     }
     return cat
   }
 }
 var cat1 = Cat.createNew()
 cat1.makeSound()

3.2 繼承

 var Animal = {
   creatNew: function() {
     var animal = {}
     animal.sleep = function() {
       console.log('睡覺覺')
     }
     return animal
   }
 }
 var Cat = {
   createNew: function() {
     var cat = Animal.creatNew()
     cat.name = '大毛'
     cat.color = 'white'
     cat.makeSound = function() {
       console.log('喵喵4')
     }
     return cat
   }
 }

var cat1 = Cat.createNew()
cat1.sleep()

3.3私有屬性和私有方法

var Cat = {
  createNew: function() {
    var cat = {}
    var sound = '喵喵喵'
    var color = '小白'
    cat.makeSound = function() {
      console.log('sound', sound)
      return color
    }
    return cat
  }
}
var cat1 = Cat.createNew()
console.log('cat1.sound', cat1.makeSound())

3.4 數據共享

var Cat = {
  sound: '喵喵喵5',
  createNew: function() {
    var cat = {}
    cat.makeSound = function() {
      console.log('Cat.sound', Cat.sound)
    }
    cat.changeSound = function(param) {
      console.log('this', this)
      this.sound = param
      // Cat.sound = param
    }
    return cat
  }
}
var cat1 = Cat.createNew()
cat1.makeSound()
cat1.changeSound('wang')
var cat2 = Cat.createNew()
cat2.makeSound()
cat1.makeSound()

test1

function Animal() {
  this.makesound = function() {
    console.log('miao~~~')
  }
}
function Cat(name, color) {
  Animal.apply(this, arguments)
  this.name = name
  this.color = color
}
const cat1 = new Cat('小喵喵', 'white')
const cat2 = new Cat('小花花', 'black')
console.log('cat1', cat1.name, cat1.color)
console.log('cat2', cat2.name, cat2.color)
cat1.makesound()
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章