JS創(chuàng)建對象、JS繼承

一、創(chuàng)建對象

  • 使用工廠模式創(chuàng)建對象
    ?????用函數來封裝以特定接口創(chuàng)建對象的細節(jié),函數createPerson()能夠根據接受的參數來創(chuàng)建一個包含所有必要信息的Person對象,可以無數次地調用這個函數,而每次它都會返回一個包含三個屬性和一個方法的對象,工廠模式雖然解決了創(chuàng)建多個相似對象的問題,但卻沒有解決對象識別的問題,即怎樣知道一個對象的類型;
function createPerson(name, age, job) {
      var o = new Object();
      o.name = name;
      o.age = age;
      o.job = job;
      o.sayName = function() {
        alert(this.name);
      };
      return o;
    }

    var person1 = createPerson('name1', 12, 'job1');
    var person2 = createPerson('name2', 34, 'job2');
  • 使用構造函數創(chuàng)建對象:
    使用new操作符創(chuàng)建對象實例的四個步驟:
    1)創(chuàng)建一個新對象;
var obj = {};

2)將構造函數的作用域賦給新對象(因此this就指向了這個新對象);

// 設置新對象的constructor屬性為構造函數的名稱;
// 設置新對象的__proto__屬性指向構造函數的prototype對象;
obj.constructor = ClassA
obj.__proto__= ClassA.prototype;

3)執(zhí)行構造函數中的代碼(為這個新對象添加屬性);

// 使用新對象調用函數,函數中的this被指向新實例對象
ClassA.call(obj);

4)返回新對象;
?????構造函數內部使用了this變量,對構造函數使用new運算符,可以生成實例,并且this變量會綁定在實例對象上;
?????使用new操作符創(chuàng)建對象

// 與工廠模式的不同之處
// 沒有顯示的創(chuàng)建對象
// 直接將屬性和方法賦給了this對象
// 沒有return語句
    function Person(name,age,job){
      this.name = name;
      this.age = age;
      this.job = job;
      this.type = 'person';
      this.sayName = function(){
        alert(this.name);
      };
    }
    var person1 = new Person('name1',23,'job1');
    var person2 = new Person('name2',12,'job2');
    console.log(person1.constructor == Person); // true
    console.log(person2.constructor == Person); // true
    console.log(person1 instanceof Object); // true
    console.log(person2 instanceof Object); // true
    console.log(person1 instanceof Person); // true
    console.log(person2 instanceof Person); // true
// 缺點:浪費內存,每個Person實例都包含一個不同的Function實例
    console.log(person1.sayName == person2.sayName); // false
    console.log(person1.type == person2.type); // true
// 改進:將sayName屬性設置成全局的sayName函數
// person1和person2對象共享了全局作用域中定義的sayName函數
// 缺點:如果對象需要定義很多方法,那就需要定義多個全局函數
function Person(name,age,job){
      this.name = name;
      this.age = age;
      this.job = job;
      this.type = 'person';
      this.sayName = sayName;
    }
    function sayName(){
      alert(this.name);
    }
    var person1 = new Person('name1',23,'job1');
    var person2 = new Person('name2',12,'job2');
    console.log(person1.constructor == Person); // true
    console.log(person2.constructor == Person); // true
    console.log(person1 instanceof Object); // true
    console.log(person2 instanceof Object); // true
    console.log(person1 instanceof Person); // true
    console.log(person2 instanceof Person); // true
    console.log(person1.sayName == person2.sayName); // true
    console.log(person1.type == person2.type); // true
  • 使用Prototype模式創(chuàng)建對象:
    ?????js規(guī)定每一個構造函數都有一個prototype屬性,指向另一個對象,這個對象的所有屬性和方法,都會被構造函數的實例繼承,因此我們可以把那些不變的屬性和方法直接定義在prototype對象上,這樣就保證了不變的屬性和方法不會被多次生成;
   function Person(){}
    Person.prototype.name = 'name1';
    Person.prototype.age = 23;
    Person.prototype.job = 'job1';
    Person.prototype.sayName = function(){
      alert(this.name);
    };
    var person1 = new Person();
    person1.sayName();
    var person2 = new Person();
    person2.sayName();
    console.log(person1.sayName == person2.sayName); // true
    console.log(Person.prototype.isPrototypeOf(person1)); // true
    console.log(Person.prototype.isPrototypeOf(person2)); // true
    person1.name = 'name2'; // 設置后可以阻止訪問原型中的屬性
    console.log(person1.hasOwnProperty('name')); // true
    delete person1.name; // delete操作符可以完全刪除實例屬性,從而可以重新訪問原型中的屬性
    console.log(person1.name); // name1
    console.log(person1.hasOwnProperty('name')); // false
    console.log('name' in person1); // true in操作符不管該屬性是位于實例還是原型中,只要能訪問到到返回true
    console.log(hasPrototypeProperty(person1,'name')); // true
    person1.name = 'name3';
    console.log(hasPrototypeProperty(person1,'name')); // false

for-in循環(huán):

// 返回所有能夠被對象訪問、可枚舉的屬性
// 屬性既包括實例中的屬性也包括原型中的屬性
// 屏蔽了原型中不可枚舉屬性的實例屬性也會for-in循環(huán)中返回
var o = {
   toString: function() {
      return 'My Object';
   }
};
for (var prop in o) {
   if (prop == 'toString') {
      alert('Found toString');
   }
}

Object.keys()方法:

// 獲取對象上所有可枚舉的實例屬性
function Person(){}
    Person.prototype.name = 'name1';
    Person.prototype.age = 25;
    Person.prototype.job = 'job1';
    Person.sayName = function(){
      alert(this.name);
    }
    var keys = Object.keys(Person.prototype);
    console.log(keys); // ["name", "age", "job"]
    var person1 = new Person();
    var person1Keys = Object.keys(person1);
    console.log(person1Keys); // []
    person1.name = 'name2';
    person1.age = 23;
    var person1Keys = Object.keys(person1);
    console.log(person1Keys); // ["name", "age"]

Object.getOwnPropertyNames()方法

 // 返回對象的所有實例屬性,包括不可枚舉的constructor屬性
function Person(){}
    Person.prototype.name = 'name1';
    Person.prototype.age = 25;
    Person.prototype.job = 'job1';
    Person.sayName = function(){
      alert(this.name);
    }
    var keys = Object.getOwnPropertyNames(Person.prototype);
    console.log(keys); // ["constructor", "name", "age", "job"]
    var person1 = new Person();
    person1.name = 'name1';
    var person1Keys = Object.getOwnPropertyNames(person1);
    console.log(person1Keys); // ['name']

二、繼承
???????原型鏈:B繼承了A,B創(chuàng)建了實例instance,當調用instance的方法時,會經歷三個步驟:1)搜索實例instance;2)搜索B.prototype;3)搜索A.prototype;最后一步才會找到要調用的方法,在找不到屬性或方法的情況下,搜索過程總是要一環(huán)一環(huán)地前行到原型鏈末端才會停下來;
???????B繼承了A,而A繼承了Object,當調用instance.toString()時,實際上調用的是保存在Object.prototype中的方法;

  • 原型鏈繼承
    function Animal() {
      this.colors = ['red', 'green', 'blue'];
    }

    function Cat() {}
    Cat.prototype = new Animal();
    var cat1 = new Cat();
    // 包含引用類型值的原型屬性會被所有實例共享
    // 創(chuàng)建子類型的實例時,不能向超類型的構造函數中傳遞參數
    cat1.colors.push('black');
    console.log(cat1.colors); // ["red", "green", "blue", "black"]

    var cat2 = new Cat();
    console.log(cat2.colors); // ["red", "green", "blue", "black"]
    // 使用prototype模式綁定
    function Animal(){
      this.species = "動物";
    }
    function Cat(name,color){
      this.name = name;
      this.color = color;
    }
    // Cat的prototype對象指向Animal的實例,Cat的所有實例也就能繼承Animal了
    Cat.prototype = new Animal();
    // 為避免繼承鏈的紊亂
    // 任何一個prototype對象都有一個constructor屬性,指向它的構造函數
    // 上一行將Cat.prototype.constructor指向了Animal,因此需要將Cat.prototype.constructor指回Cat
    // 每一個實例都有一個constructor屬性,默認調用prototype對象的constructor屬性
    Cat.prototype.constructor = Cat;
    var cat1 = new Cat("大毛","黃色");
    alert(Cat.prototype.constructor == Animal); // false
    alert(cat1.constructor == Cat.prototype.constructor); // true
    alert(cat1.constructor == Animal); // false
    alert(cat1.species); // 動物
  • 借用構造函數
    // 使用構造函數綁定
    function Animal(age) {
      this.age = age;
      this.species = "動物";
      this.colors = ['red','green','blue'];
    }

    function Cat(name, color) {
      // 繼承了Animal,同時傳遞了參數
      Animal.call(this, 24); // 此處應使用call方法
      this.name = name;
      this.color = color;
    }
    var cat1 = new Cat("大毛", "黃色");
    cat1.colors.push('black');
    console.log(cat1.colors); // ["red", "green", "blue", "black"]
    console.log(cat1.species); // 動物
    console.log(cat1.age); // 24
    var cat2 = new Cat('二毛','白色');
    // 解決了引用類型值的原型屬性會被所有實例共享的問題
    console.log(cat2.colors); // ["red", "green", "blue"]
    // 缺陷:方法都在構造函數中定義了,函數復用就無從談起了
  • 組合繼承:
    // 將原型鏈和借用構造函數技術組合到一起,集二者之長
   // 使用instanceof和isPrototypeOf可以識別基于組合繼承創(chuàng)建的對象
   function Animal(name){
      this.name = name;
      this.colors = ['red','blue','green'];
    }
    Animal.prototype.sayName = function(){
      console.log(this.name);
    }
    function Cat(name,age){
      Animal.call(this,name); // 繼承屬性,第二次調用Animal()
      this.age = age;
    }
    // 繼承方法
    Cat.prototype = new Animal(); // 第一次調用Animal()
    Cat.prototype.sayAge = function(){
      console.log(this.age);
    }
    var cat1 = new Cat('大毛',3);
    cat1.colors.push('black');
    console.log(cat1.colors); // ["red", "blue", "green", "black"]
    cat1.sayName(); // 大毛
    cat1.sayAge(); // 3

    var cat2 = new Cat('二毛',2);
    console.log(cat2.colors); // ["red", "blue", "green"]
    cat2.sayName(); // 二毛
    cat2.sayAge(); // 2
  • 原型式繼承:
    // 必須有一個對象來作為原型
    // object方法對傳入其中的對象執(zhí)行了一次淺拷貝
    // 缺陷:包含引用類型值的屬性始終共享相應的值
    function object(o) {
      function F() {}
      F.prototype = o;
      return new F();
    }
    var animal = {
      name: '大毛',
      colors: ['red', 'green', 'blue']
    }
    var cat1 = object(animal);
    cat1.name = '二毛';
    cat1.colors.push('black');

    var cat2 = object(animal);
    cat2.name = '三毛';
    cat2.colors.push('yellow');
    console.log(animal.colors); // ["red", "green", "blue", "black", "yellow"]
  // Object.create()方法
   var animal = {
      name: '大毛',
      colors: ['red', 'green', 'blue']
    }
    var cat1 = Object.create(animal);
    cat1.name = '二毛';
    cat1.colors.push('black');

    var cat2 = Object.create(animal);
    cat2.name = '三毛';
    cat2.colors.push('yellow');
    console.log(animal.colors); // ["red", "green", "blue", "black", "yellow"]
    // object方法
    var Chinese = {
      nation: '中國'
    };
    // 將子對象的prototype屬性指向父對象,從而使得子對象與父對象連在一起
    function object(o){
      function F(){}
      F.prototype = o;
      return new F();
    }
    var Doctor = object(Chinese);
    Doctor.career = '醫(yī)生';
    alert(Doctor.nation); // 中國
  • 寄生式繼承:
   function object(o) {
      function F() {}
      F.prototype = o;
      return new F();
    }
    // 與寄生構造函數和工廠模式類似
    // 創(chuàng)建一個僅用于封裝繼承過程的函數,該函數在內部以某種方式來增強對象,最后再返回對象
    function createAnother(original) {
      var clone = object(original); // 通過調用函數創(chuàng)建一個新對象
      clone.sayHi = function() { // 以某種方式來增強這個對象,但不能做到函數復用使得效率不高
        console.log('hi'); // 新對象不僅有original的所有屬性和方法,而且還有自己的sayHi方法
      };
      return clone; // 返回這個對象
    }

    var animal = {
      name: '大毛',
      colors: ['red', 'green', 'blue']
    };
    var cat = createAnother(animal);
    cat.sayHi(); // hi
  • 寄生組合式繼承
   function object(o) {
      function F() {}
      F.prototype = o;
      return new F();
    }

    function inheritPrototype(cat, animal) {
      var prototype = object(animal.prototype); // 創(chuàng)建超類型原型的一個副本
      // 增強對象,為創(chuàng)建的副本添加constructor屬性,從而彌補因重寫原型而失去的默認constructor屬性
      prototype.constructor = cat;
      cat.prototype = prototype; // 將新創(chuàng)建的副本對象賦值給了類型的原型
    }

    function animal(name) {
      this.name = name;
      this.colors = ['red', 'blue', 'green'];
    }
    animal.prototype.sayName = function() {
      console.log(this.name);
    }

    function cat(name, age) {
      animal.call(this, name);
      this.age = age;
    }
    inheritPrototype(cat, animal);
    cat.prototype.sayAge = function() {
      console.log(this.age);
    }
    var cat1 = new cat('二毛', 3);
    cat1.sayName(); // 二毛
    cat1.sayAge(); // 3
  • 直接繼承prototype(紅寶書未提到,阮一峰提到的一種方法)
    // 直接繼承prototype
    function Animal(){}
    Animal.prototype.species = "動物";
    function Cat(name,color){
      this.name = name;
      this.color = color;
    }
    // Cat()直接跳過Animal(),直接繼承Animal.prototype
    Cat.prototype = Animal.prototype;
    Cat.prototype.constructor = Cat;
    var cat1 = new Cat("大毛","黃色");
    alert(cat1.species); // 動物
    // 此時Cat.prototype和Animal.prototype現在指向了同一個對象
    // 任何對Cat.prototype的修改都會反應到Animal.prototype
    alert(Animal.prototype.constructor); // Cat
  • 利用空對象作為中介(其實就是原型式繼承方法)
    // 利用空對象作為中介
    function Animal(){
      this.species = "動物";
    }
    function Cat(name,color){
      this.name = name;
      this.color = color;
    }
    // F是空對象幾乎不占內存,此時修改Cat的prototype對象,不會影響到Animal的prototype對象
    var F = function(){};
    F.prototype = Animal.prototype;
    Cat.prototype = new F();
    Cat.prototype.constructor = Cat;
    alert(Animal.prototype.constructor); // Animal
    // 改進版
    function Animal(){
      this.species = "動物";
    }
    function Cat(name,color){
      this.name = name;
      this.color = color;
    }
    function extend(Child,Parent){
      var F = function(){};
      F.prototype = Parent.prototype;
      Child.prototype = new F();
      Child.prototype.constructor = Child;
      // 為子對象設一個uber屬性,這個屬性直接指向父對象的prototype屬性,
      // 這相當于在子對象上打開了一條通道,可以直接調用父對象的方法,實現繼承的完備性
      Child.uber = Parent.prototype;
    }
    extend(Cat,Animal);
    var cat1 = new Cat("大毛","黃色");
    alert(cat1.species);
  • 拷貝繼承(網上的一種方法)
    // 拷貝繼承
    function Animal(){}
    Animal.prototype.species = "動物";
    function Cat(name,color){
      this.name = name;
      this.color = color;
    }
    // 將父對象的prototype對象中的屬性一一拷貝給Child對象的prototype對象
    function extend2(Child,Parent){
      var p = Parent.prototype;
      var c = Child.prototype;
      for(var i in p){
        c[i] = p[i];
      }
      c.uber = p;
    }
    extend2(Cat,Animal);
    var cat1 = new Cat("大毛","黃色");
    alert(cat1.species); // 動物

三、深拷貝和淺拷貝

  • 淺拷貝方法
    // 淺拷貝方法
    var Chinese = {
      nation: '中國'
    };
    // 將父對象的屬性全部拷貝給子對象,也能實現繼承
    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 = '醫(yī)生';
    alert(Doctor.nation); // 中國
    // 存在的問題
    // 如果父對象的屬性等于數組或另一個對象,那么子對象獲得的只是一個內存地址
    // 而不是真正的拷貝,因此存在父對象被篡改的可能
    var Chinese = {
      nation: '中國'
    };
    Chinese.birthPlaces = ['北京','上海','香港'];
    // 將父對象的屬性全部拷貝給子對象,也能實現繼承
    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 = '醫(yī)生';
    Doctor.birthPlaces.push('廈門');
    alert(Doctor.birthPlaces); // 北京,上海,香港,廈門
    alert(Chinese.birthPlaces); // 北京,上海,香港,廈門
  • 深拷貝
    // 深拷貝
    var Chinese = {
      nation: '中國'
    };
    Chinese.birthPlaces = ['北京', '上海', '香港'];
    // 實現對象和數組和深拷貝
    function deepCopy(p, c) {
      var c = c || {};
      for (var i in p) {
        if (typeof p[i] === 'object') {
          c[i] = (p[i].constructor === Array) ? [] : {};
          deepCopy(p[i],c[i]);
        }else{
          c[i] = p[i];
        }
      }
      return c;
    }
    var Doctor = deepCopy(Chinese);
    Doctor.birthPlaces.push('廈門');
    alert(Doctor.birthPlaces); // 北京,上海,香港,廈門
    alert(Chinese.birthPlaces); // 北京,上海,香港

總結:

  • 原型鏈繼承
    function Animal(){
      this.colors = ['red','blue','green'];
    }
    function Cat(){}
    Cat.prototype = new Animal();
  • 借用構造函數繼承
    function Animal(){
      this.colors = ['red','blue','green'];
    }
    function Cat(){
      Animal.call(this)
    }
  • 組合繼承
    function Animal(){
      this.colors = ['red','blue','green'];
    }
    Animal.prototype.sayColors = function(){
      console.log(this.colors);
    }
    function Cat(){
      Animal.call(this); // 繼承屬性
    }
    Cat.prototype = new Animal(); // 繼承方法
  • 原型式繼承
    function object(o) {
      function F() {}
      F.prototype = o;
      return new F();
    }
    var animal = {
      name: '大毛',
      colors: ['red', 'green', 'blue']
    }
    var cat1 = object(animal);
  • 寄生式繼承
   function object(o) {
      function F() {}
      F.prototype = o;
      return new F();
    }
    function createAnother(original) {
      var clone = object(original); 
      clone.sayHi = function() { 
        console.log('hi'); 
      };
      return clone; 
    }

    var animal = {
      name: '大毛',
      colors: ['red', 'green', 'blue']
    };
    var cat = createAnother(animal);
  • 寄生組合式繼承:
   function object(o) {
      function F() {}
      F.prototype = o;
      return new F();
    }

    function inheritPrototype(cat, animal) {
      var prototype = object(animal.prototype); 
      prototype.constructor = cat;
      cat.prototype = prototype; 
    }

    function animal(name) {
      this.name = name;
      this.colors = ['red', 'blue', 'green'];
    }
    animal.prototype.sayName = function() {
      console.log(this.name);
    }

    function cat(name, age) {
      animal.call(this, name);
      this.age = age;
    }
    inheritPrototype(cat, animal);
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • 這是16年5月份編輯的一份比較雜亂適合自己觀看的學習記錄文檔,今天18年5月份再次想寫文章,發(fā)現簡書還為我保存起的...
    Jenaral閱讀 3,123評論 2 9
  • 函數和對象 1、函數 1.1 函數概述 函數對于任何一門語言來說都是核心的概念。通過函數可以封裝任意多條語句,而且...
    道無虛閱讀 4,944評論 0 5
  • ??面向對象(Object-Oriented,OO)的語言有一個標志,那就是它們都有類的概念,而通過類可以創(chuàng)建任意...
    霜天曉閱讀 2,255評論 0 6
  • 第一部——追夢人 當清晨第一縷陽光 灑在蔚藍的海面上 你說,你要去遠方 去尋找傳說中的天堂 你說,曾經有個陌生人 ...
    慕容錦瑟閱讀 456評論 0 2
  • 下班后是十二點半,在阿卜美食廣場隨便吃了份雜醬刀削面,味道還不錯,碗很大,面分量也不少,反正吃的很飽了。然后...
    顧影之蓮閱讀 165評論 0 0

友情鏈接更多精彩內容