首頁 > web前端 > js教程 > 主體

關於JavaScript的原型繼承的超詳細介紹

零到壹度
發布: 2018-04-13 17:20:37
原創
1258 人瀏覽過

這篇文章給大家分享的內容是關於JavaScript的原型繼承,有著一定的參考價值,有需要的朋友可以參考一下

一、理解物件

     ECMAScript有兩種屬性: 資料屬性和存取器屬性。

   1、 資料屬性

    資料屬性有4個描述其行為的特性:Configurable、Enumerable、Writable、Value。

    Configurable:表示能否透過delete刪除屬性以重新定義屬性,能否修改屬性的特性,並且能把屬性修改為存取器屬性。對於在物件上直接定義的屬性,預設為true. 
    Enumerable:表示能否透過for-in循環回傳屬性。 
    Writable:表示能否修改屬性的值。 
    Value:包含這個屬性的資料值。
    要修改屬性預設的特性,可使用ECMAScript5的Object.defineProperty()方法。接收三個參數:屬性所在的物件、屬性的名字、一個描述符物件。如下:

var person = {};
Object.defineProperty(person,"name",{
    writable:false;
    value:"nichols";
    Configurable:false;
});
alert(person.name);//"nichols"
person.name = "greg";//严格模式下会抛错.非严格模式下忽略。
delete person.name;//严格模式下会抛错
alert(person.name);//nichols
登入後複製

2、存取器屬性

    包含一對getter和setter函數。有四個特性:Configurable、Enumerable、Get、Set。
    Configurable:表示能否透過delete刪除屬性因而重新定義屬性,能否修改屬性的特性,並且能把屬性修改為存取器屬性。對於在物件上直接定義的屬性,預設為true. 

    Enumerable:表示能否透過for-in循環傳回屬性。

    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;
alert(book.edition);//2
登入後複製

    不一定要同時指定getter和seter.只指定getter意味著不能寫入。
    歷史遺留的兩個方法:

var book = {
    _year:2004,
    edition:1
};
book.__defineGetter__("year",function{return this._year});
book.__defineSetter__("year",function{.....});
登入後複製

3、同時定義多個屬性

    Object.defineProperties():接收兩個物件參數,若要新增或修改屬性的物件和對應的要新增或修改屬性。

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;
          }
        }
    }
});
登入後複製

4、讀取屬性的特性

    使用ECMAScript5的Object.getOwnPropertyDescriptor()方法可以取得給定物件的描述子。這個方法接收兩個參數:屬性所在的物件和要讀取其描述符的屬性名稱。傳回值是一個物件。

var descriptor = Object.getOwnPropertyDescrptor(book,"_year");
alert(descriptor.value);//2004
alert(descriptor.configurable);//false
alert(typeof descriptor.get);//undefined
var descriptor = Object.getOwnPropertyDescrptor(book,"year");
alert(descriptor.value);//undefined
alert(descriptor.enumerable);//false
alert(typeof descriptor.get);//function
登入後複製

5、函數與物件的關係

    物件都是透過函數建立的。

function Fn() {
            this.name = 'yzh';
            this.year = 1996;
        }
        var fn1 = new Fn();
登入後複製

有人可能會舉出如下反例

var obj = { a: 10, b: 20 };
var arr = [5, 'x', true];
登入後複製

這種做法屬於使用“快捷方式”,在程式語言中,一般叫做“語法糖”。

其實以上程式碼的本質是:

//var obj = { a: 10, b: 20 };
//var arr = [5, 'x', true];
        var obj = new Object();
        obj.a = 10;
        obj.b = 20;
        var arr = new Array();
        arr[0] = 5;
        arr[1] = 'x';
        arr[2] = true;
登入後複製

而其中的Object 和Array 都是函數:

console.log(typeof (Object));  // function
console.log(typeof (Array));  // function
登入後複製

二、建立物件

# 1.工廠模式

    用函數來封裝以特定介面建立物件的細部。

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("nichils",29,"softward engineer");
登入後複製

2、建構子模式

function Person(name, age, job){
            this.name = name;
            this.age = age;
            this.job = job;
            this.sayName = function(){
                alert(this.name);
            };    
        }
var person1 = new Person("Nicholas", 29, "Software Engineer");
 var person2 = new Person("Nicholas", 29, "Software Engineer"); 
 alert(person1.sayName == person2.sayName)//false 
 alert(person1 instanceof Object);  //true
        alert(person1 instanceof Person);  //true
   alert(person1.constructor == Person);  //true
登入後複製

要建立person的新實例,必須使用new運算元。以這種方式呼叫建構函數其實會經歷以下4個步驟: 
1. 建立一個新物件 
2. 將建構函數的作用域賦給新物件。 
3. 執行建構函式中的程式碼 
4. 傳回新物件。 
上面的person有一個constructor屬性,該屬性指向Person. 
總結:建立自訂的建構子意味著將來可以將它的實例標識為特定的類型。任何函數只要透過new操作符來來調用,那他就可以當作建構子。而不用new就跟普通函數沒什麼差別。 
建構函數的問題:使用建構函數的主要問題是每個方法都要在每個實例上重新建立一次。 
可以把函數定義轉移到建構函數外來解決這個問題。

 function Person(name, age, job){
            this.name = name;
            this.age = age;
            this.job = job;
            this.sayName = sayName;
        }
        function sayName(){
            alert(this.name);
        }
登入後複製

    而這樣會帶來新的問題:如果物件需要定義很多方法那麼就要定義很多個全域函數。可以透過原型模式來解決。

3、原型模式

    我們創建的每個函數都有一個prototype屬性,這個屬性是個指針,指向一個對象,這個對象包含可以由特定類型的所有實例共享的屬性和方法。 
    使用原型物件的好處:不必在建構函數中定義物件實例的訊息,可以將這些資訊直接加入原型物件中。

function Person(){
        }
        Person.prototype.name = "Nicholas";
        Person.prototype.age = 29;
        Person.prototype.job = "Software Engineer";
        Person.prototype.sayName = function(){
            alert(this.name);
        };
        var person1 = new Person();
        person1.sayName();   //"Nicholas"
        var person2 = new Person();
        person2.sayName();   //"Nicholas"
        alert(person1.sayName == person2.sayName);  //true
登入後複製

3.1 理解原型

原型对象的这些属性和方法是由所有实例共享的。 所有原型对象都会自动获得一个constructor属性,这个属性包含一个指向prototype所在函数的指针。所以 Person.prototype.constructor = Person. 当调用构造函数的新实例后,该实例的内部也会有一个指针叫[[Prototype]]指向构造函数的原型对象而非构造函数。虽然在脚本中没有标准的方式访问[[Prototype]],但Firefox、Safari和Chrome在每个对象上都支持一个属性_proto_;而在其他实现中,这个属性对脚本则是完全不可见的。不过,要明确的真正重要的一点就是,这个连接存在于实例和构造函数的原型对象之间,而不是存在于实例与构造函数之间。


所有实现中都无法访问 [[Prototype]],但可以调用isPrototypeOf()方法来判断这种关系。

alert(person1.prototype.isPrototypeOf(person1));//true
登入後複製

或者通过Object.getPropertyOf()来得到[Prototype]的值。即这个对象的原型。

alert(Object.getPrototypeOf(person1)==Person.prototype);//true
登入後複製

每当读取一个对象的属性时,首先先搜索对象实例本身的属性,找到了就返回。找不到再去搜索原型对象的属性。找到了就返回。 原型最初只包含constructor属性,而该属性也是共享的。因此可以通过对象实例访问。
如果在实例中添加了一个与对象原型中的属性同名的属性,则在实例中创建该属性,并且屏蔽原型中的那个属性。

function Person(){
        }
        Person.prototype.name = "Nicholas";
        var person1 = new Person();
        var person2 = new Person();
        person1.name = "Greg";
        alert(person1.name);   //"Greg" ?from instance
        alert(person2.name);   //"Nicholas" ?from prototype
登入後複製

使用delete操作符可以完全删除实例属性,从而让我们能够重新访问原型中的属性。

使用hasOwnProerty()可以检测一个属性是存在于实例中还是存在于原型中。如果存在于对象实例中则返回true.

  var person1 = new Person();
  var person2 = new Person();
  alert(person1.hasOwnProperty("name"));  //false
  person1.name = 'Greg';
  alert(person1.name); //"Greg"——来自实例
  alert(person1.hasOwnProperty("name"));  //true
  alert(person2.name); //"Nicholas"——来自原型
  alert(person2.hasOwnProperty("name")); //false
  delete person1.name;
  alert(person1.name); //"Nicholas"——来自原型
  alert(person1.hasOwnProperty("name")); //false
登入後複製


3.2原型与in操作符

两种使用方法:单独使用和在for-in循环中使用。单独使用时,如果通过对象访问能够给定属性,in操作符会返回true,无论该对象存在于实例中还是原型中。

alert("name" in person1);//true
person1.name = "kke";
alert("name" in person1);//true
登入後複製

使用for-in循环返回的是所有能够通过对象访问的、可枚举的属性。不管该属性是在实例中还是在原型中。所有开发人员定义的属性都是可枚举的。
注:IE8及更早版本的实现中存在一个bug,及屏蔽不可枚举属性的实例属性不会出现在for-in循环中。

 var o = {
            toString : function(){
                return "My Object";
            }
        }
        for (var prop in o){
            if (prop == "toString"){
                alert("Found toString");//ie中中不显示
            }
        }
登入後複製

可以通过ECMAScript5的Object.keys()方法来取得对象上所有可枚举的实例属性。接收一个对象参数,返回一个包含该对象所有可枚举属性的字符串数组。

function Person(){
        }
        Person.prototype.name = "Nicholas";
        Person.prototype.age = 29;
        Person.prototype.job = "Software Engineer";
        Person.prototype.sayName = function(){
            alert(this.name);
        };
var keys = Object.keys(Person.prototype);
alert(keys);   //"name,age,job,sayName"
var p1 = new Person();
p1.name = 'rob';
pa.age=13;
alert(Object.keys(p1));//name,age.
登入後複製

getOwnPropertyNames():得到所有的实例属性无论是否可枚举。

 var keys = Object.getOwnPropertyNames(Person.prototype);
alert(keys);   //"constructor,name,age,job,sayName"
登入後複製

3.3更简单的原型语法

 function Person(){
        }
Person.prototype = {
            name : "Nicholas",
            age : 29,
            job: "Software Engineer",
            sayName : function () {
                alert(this.name);
            }
        };
登入後複製

但此时原型对象的constructor属性不再指向Person,而是指向Object构造函数。尽管通过instanceof还能返回正确的结果。

 var friend = new Person();
        alert(friend instanceof Object);  //true
        alert(friend instanceof Person);  //true
        alert(friend.constructor == Person);  //false
        alert(friend.constructor == Object);  //true
登入後複製

3.4原型的动态性

对原型对象的任何修改都能够立即从实例上反映出来。

 var friend = new Person();
  Person.prototype.sayHi = function(){
         alert("hi");
        };
friend.sayHi();   //hi
登入後複製

而如果是把原型改为另外一个对象,就等同于切断构造函数与最初原型之间的联系。而实例中的指针是指向最初原型的。因此会出错。

 function Person(){
        }
        var friend = new Person();        
        Person.prototype = {
            constructor: Person,
            name : "Nicholas",
            age : 29,
            job : "Software Engineer",
            sayName : function () {
                alert(this.name);
            }
        };
        friend.sayName();   //error
登入後複製


3.5原型对象的问题

对于包含引用类型的属性的原型对象,所有实例共享的都是同一个引用类型。

function Person(){
        }
        Person.prototype = {
            constructor: Person,
            name : "Nicholas",
            age : 29,
            job : "Software Engineer",
            friends : ["Shelby", "Court"],
            sayName : function () {
                alert(this.name);
            }
        };       
var person1 = new Person();
var person2 = new Person();
person1.friends.push("Van");      
alert(person1.friends);    //"Shelby,Court,Van"
alert(person2.friends);    //"Shelby,Court,Van"
alert(person1.friends === person2.friends);  //true
登入後複製

实例一般都会有自己全部的属性的,因此这个问题是很少有人单独使用原型模式的原因所在。

4、组合使用构造函数模式和原型模式

创建自定义类型最常见的方式就是组合使用构造函数模式和原型模式。构造函数模式用于定义实力属性,而原型模式用于定义方法和共享的属性。结果,每个实例都会有自己的一份实例属性的副本,但同时又共享着对方法的引用,最大限度地节省了内存。另外,这种混成模式还支持向构造函数传递参数。

function Person(name ,age,job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.friends = ["Shelby", "Court"];
}
Person.ptototype = {
    constructor:Person,
    sayName:function(){
    alert(this.name);
    }
}
var person1 = new Person("hah",32,"doctor");
var person2 = new Person("hah",32,"doctor");
person1.friends.push("van");
alert(person1.friends);Shelby,Court,van
alert(person2.friends);Shelby,Court
alert(person1.sayName = person2.sayName);//true
登入後複製

5、动态原型模式

把所有信息封装在构造函数中,在构造函数中初始化原型。既可以通过检查某个应该存在的方法是否有效来决定师傅需要初始化原型。

function Person(name ,age,job){
    this.name = name;
    this.age = age;
    this.job = job;
    //方法
    if(typeof this.sayName != "function"){
        Person.prototype.sayName = function(){
            alert(this.name);
        };
    }
}
登入後複製

只在sayName方法不存在的情况下,才会将它添加到原型中。即只在初次调用构造函数时才会执行。if语句检查的可以是初始化之后应该存在的任何属性或方法。只需检查一个就行。若不存在则初始化原型的所有属性或方法。
注:使用动态原型模式不能使用对象字面量来重写原型,因为如果在已经创建了实例的情况下重写原型,就会切断现有实例与原型之间的联系。

6、寄生构造函数模式

在函数中创建一个新对象,然后返回新对象。

function SpecialArray(){
    var values = new Array();
    values.push.apply(values,argument);
    values.toPipedString = function(){
        return this.join("|");
    }
    return values;
}
var colors = new SpecialArray("red","blue","green");
alert(colors.topipedString());//red|blue|green
登入後複製

注:使用这种方式不能使用instaneof来确定对象类型。可以使用其他模式的情况下建议不要使用这种模式。

7、稳妥构造函数模式

稳妥对象指的是没有公共属性,而且其方法也不引用this的对象。适合在一些安全的环境中或者防止数据被其他应用程序改动时使用。两个特点:

1. 新创建对象的实例方法不引用this

2. 不使用new擦操作符调用构造函数。

function Person(name,age,job){
    var 0 = new Object();
    //定义私有变量和函数
    //添加方法
    o.sayName = function(){
        alert(name);
    }
    return o;
}
var friends = Person("hdkl",23,"dlksl");
friends.sayName();//hdkl
登入後複製


方法

细节

工厂模式

用函数来封装以特定接口创建对象的细节。

优点:可以无数次调用函数。

缺点:但没有解决对象识别的问题。

构造函数模式

没有显示地创建对象;

直接将属性和方法赋给了this对象;

没有return语句。

构造函数始终都以一个大写字母开头,而非构造函数应该以一个小写字母开头。

要创建构造函数的新实例,必须使用new操作符。

优点:构造函数模式胜过工厂模式的地方在于:可以将它的实例标识为一种特定的类型。

缺点:每个方法都要在每个实例上重新创建一遍。

原型模式

每个函数都有一个prototype(原型)属性,此属性是一个指针,指向一个对象,此对象的用途是包含可以由特定类型的所有实例共享的属性和方法。

当为对象实例添加一个属性时,这个属性就会屏蔽原型对象中保存的同名属性,但不会修改那个属性。使用delete操作符则可以完全删除实例属性,能够重新访问原型中的属性。

hasOwnProperty( )方法可以检测一个属性是存在于实例中,还是存在于原型中。只有存在于对象实例中,才会返回true。

单独使用时,in操作符会在通过对象能够访问给定属性时返回true,无论该属性存在于实例中还是原型中。只要in操作符返回true,而hasOwnProperty( )返回false,就可以确定属性时原型中的属性。

hasPrototypeProperty( )方法,当原型属性存在时,返回true,当原型属性被实例重写时,返回false。

在使用for-in循环时,返回的是所有能够通过对象访问的、可枚举的属性,其中既包括存在于实例中的属性,也包括存在于原型中的属性。

重写原型对象切断了现有原型与任何之前已经存在的对象实例之间的联系,他们引用的仍然是最初的原型。

优点:可以让所有对象实例共享它所包含的属性和方法,即不必再构造函数中定义对象实例的信息,而是可以将这些信息直接添加到原型对象中。

缺点:由共享本质,对于包含引用类型值的属性而言问题突出。

组合使用构造函数模式和原型模式

建立自訂最常見的方式,也是用來定義參考類型的預設形式。

實例屬性在建構函式中定義,所有實例共享的屬性constructor和方法都在原型中定義。

優點:集合建構子與原型模式之所長。

動態原型模式

#使用if語句檢查初始化之後應該存在的任何屬性或方法。採用此模式所建立的物件可以用instanceof操作符確定它的類型。

寄生建構子模式

#除了使用new運算子並把使用的包裝函數叫做建構子之外,此模式與工廠模式是一樣的。建構函式在不傳回值的情況下,預設會傳回新物件實例,而透過在建構函式的末端新增一個return語句,可以重寫呼叫建構函式傳回的值。

傳回的物件與建構函式或建構函式的原型屬性直接沒有關係。

不能依賴instanceof運算子來決定物件類型。

可以使用其他模式,盡量不要使用該模式

#穩健建構子模式

穩妥物件:沒有公共屬性,其方法也不引用this的物件。

與寄生建構模式的兩點不同:一是新建立物件的實例方法不引用this;二是不使用new運算元呼叫建構函式。


#

三、继承

ECMAScript只支持实现继承,而且依靠原型链实现

1、原型链

ECMAScript中描述了原型链的概念,并将原型链作为实现继承的主要方法。其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。简单回顾一下构造函数、原型和实例的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。那么,假如我们让原型对象等于另一个类型的实例,结果会怎么样呢?显然,此时的原型对象将包含一个指向另一个原型的指针,相应地,另一个原型中也包含着一个指向另一个构造函数的指针。假如另一个原型又是另一个类型的实例,那么上述关系依然成立,如此层层递进,就构成了实例与原型的链条。这就是所谓原型链的基本概念。

让原型对象等于另一个类型的实例,而这个原型对象又指向另一个原型,如此层层递进构成了原型链。 实现原型链有一种基本模式:

function SuperType(){
    this.property = true;
}
SuperType.prototype.getSuperValue = function(){
    return this.property;
}
function SubType(){
    this.subproperty = false;
}
//继承了SuperType
SubType.prototype = new SuperType();
SubType.protoType.getSubValue = function(){
    return this.subproperty;
};
var instance = new SubType();
alert(instance.getSuperValue());//true
登入後複製

该继承的本质是重写原型对象,代之以一个新类型的实例。intance指向SubType的原型,SubType的原型又指向SuperType的原型。此时instance.constructor现在指向的是SuperType。现在的搜索过程是沿着原型链向上查找。上面的例子是这样的:
1. 搜索实例
2. 搜索SubType.prototype
3. 搜索SuperType.protoType


1.1别忘记默认的原型

所有引用类型都默认继承了Object,而这个继承也是通过原型链实现的。所有函数的默认原型都是Object的实例,因此默认原型都会包含一个指向Object.prototype的内部指针。

1.2确定原型和实例的关系

使用instanceof操作符,只要用这个操作符来测试实例与原型链中出现过得构造函数,结果就会返回true.

alert(instance instanceof Object);//true;
alert(instance instanceof SuperType);//true;
alert(instance instanceof SubType);//true;
登入後複製

使用isPrototypeOf()方法,只要原型链中出现过的原型,都可以说是该原型链所派生的实例的原型,都会返回true.

alert(Object.prototype.isPrototypeOf(instance));    //true
        alert(SuperType.prototype.isPrototypeOf(instance)); //true
        alert(SubType.prototype.isPrototypeOf(instance));   //true
登入後複製

1.3谨慎地定义方法

在重写超类中的方法或添加方法时要注意,给原型添加方法的代码一定要放在替换原型的语句之后。 还有就是在通过原型链实现继承时,不能使用对象字面量创建原型方法,因为会重写原型链。

function SuperType(){
    this.property = true;
}
SuperType.prototype.getSuperValue = function(){
    return this.property;
};
function SubType(){
    this.subproperty=false;
}
//继承了SuperType
SubType.prototype = new SuperType();
//添加新方法
SubType.prototype.getSubValue=function(){
    return this.subproperty;
};
//重写超类型中的方法
SubType.prototype.getSuperValue=function(){
    return false;
};
var instance = new SubType();
alert(instance.getSuperValue()); //false;
登入後複製

重写的方法getSuperValue()是原型链中已经存在的一个方法,但重写这个方法将会屏蔽原来的那个方法。换句话说,当通过SubType的实例调用getSuperValue()时,调用的就是这个重新定义的方法;但通过SuperType的实例调用getSuperValue()时,还会继续调用原来的那个方法。这里要格外注意的是,必须在用SuperType的实例替换原型之后,再定义这两个方法。

还有一点需要注意,即在通过原型链实现继承时,不能使用对象字面量创建原型方法。因为这样做就会重写原型链。

1.4原型链的问题

原型链虽然很强大,可以用它来实现继承,但它也存在一些问题。其中,最主要的问题来自包含引用类型值的原型。想必大家还记得,我们前面介绍过包含引用类型值的原型属性会被所有实例共享;而这也正是为什么要在构造函数中,而不是在原型对象中定义属性的原因。在通过原型来实现继承时,原型实际上会变成另一个类型的实例。于是,原先的实例属性也就顺理成章地变成了现在的原型属性了。

下列代码可以说明这个问题

function SuperType(){
    this.colors={"red","blue","green"};
}
function SubType(){
}
//继承了SuperTYpe
SubType.prototype=new SuperType();
var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors; //"red,blue,green.black"
var instance2 = new SubType();
alert(instance1.colors; //"red,blue,green.black"
登入後複製

这个例子中的SuperType构造函数定义了一个colors属性,该属性包含一个数组(引用类型值)。SuperType的每个实例都会有各自包含自己数组的colors属性。当SubType通过原型链继承了SuperType之后,SubType之后,SubTYpe.prototype就变成SuperType的一个实例,因此它也拥有了一个它自己的colors属性——就跟专门创建了一个SubType.prototype.colors属性一样。但结果是什么呢?结果是SubType的所有实例都会共享这一个colors属性。而我们对instance1.colors的修改能够通过instance2.colors反映出来,就已经充分证明了这一点。

原型链的第二个问题是:在创建子类型的实例时,不能向超类型的构造函数中传递参数。实际上,应该说是没有办法在不影响所有对象实例的情况下,给超类型的构造函数传递参数。

2、借用构造函数

在子类型构造函数的内部调用超类型构造函数。

function SuperType(){
    this.colors = {"red","blue"};
}
function SubType(){
    SuperType.call(this);
}
var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors);//red,blue,black
var instance2= new SubType();
alert(instance2.colors);//red,blue
登入後複製

结果就是SubType的每个实例都会具有自己的colors属性的副本了。

3、组合继承

使用原型链实现对原型属性和方法的继承,借用构造函数实现对实例属性的继承。

function SuperType(name){
    this.name = name;
    this.colors = {"red","blue"};
}
SuperType.prototype.sayName = function(){
    alert(this.name);
};
function SubType(name,age){
    //继承属性
    SuperType.call(this,name);
    this.age = age;
}
//继承方法
SubType.prototype = new SuperType();
登入後複製

这是js中最常见的继承方法。

4、原型式继承

Object.create():接收两个参数,一个是用作原型的对象,一个是为新对象定义个额外属性的对象(可选)

var person = {
            name: "Nicholas",
            friends: ["Shelby", "Court", "Van"]
        };

        var anotherPerson = Object.create(person);
        anotherPerson.name = "Greg";
        anotherPerson.friends.push("Rob");

        var yetAnotherPerson = Object.create(person);
        yetAnotherPerson.name = "Linda";
        yetAnotherPerson.friends.push("Barbie");

        alert(person.friends);   //

        var anotherPerson = Object.create(person, {
            name: {
                value: "Greg"
            }
        });

        alert(anotherPerson.name);  //"Greg"
登入後複製

5、寄生式继承

创建一个仅用于封装继承过程的函数,该函数在内部以某种方法来增强对象。

function createAnother(original){
    var clone = Object(original);
    clone.sayHi = function(){
        alert("hi");
    }
    return clone;
}
var person = {
            name: "Nicholas",
            friends: ["Shelby", "Court", "Van"]
        };
 var anotherPerson = createAnother(person);
登入後複製

6、寄生组合式继承

组合继承的问题就是会调用两次超类型构造函数。一次是在创建子类型原型的时候,一次是在子类型构造函数内部。

寄生组合式继承:使用寄生式继承来继承超类型的原型。

function inheriPrototype(subType,superType){
    var prototype = object(superType.ptototype);
    prototype.constructor = subType;
    subType.prototype = prototype;
};
function SuperType(name){
    this.name = name;
    this.colors = {"red","blue"};
}
SuperType.prototype.sayName = function(){
    alert(this.name);
};
function SubType(name,age){
    //继承属性
    SuperType.call(this,name);
    this.age = age;
}
inheriPrototype(SubType,SuperType)
登入後複製

    js最理性的继承方式



方法

实现

原型链

利用原型让一个引用类型继承另一个引用类型的属性和方法。每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。

原型链的两大问题,一是来自包含引用类型值的原型,另一个是在创建子类型的实例时,不能向超类型的构造函数中传递参数。

借用构造函数

使用apply()和call( )方法在新创建的对象上执行构造函数。

优点:相对于原型链而言,可以在子类型构造函数中向超类型构造函数传递参数

缺点:方法都在构造函数中定义,因此函数复用就无从谈起。

组合集成

将原型链和借用构造函数的技术一起,取长处的方式。原理是使用原型链实现对原型属性和方法的集成,而通过借用构造函数来实现对实例属性的继承。

优点:避免了原型链和借用构造函数的缺陷,融合了它们的优点,成为JavaScript中最常用的继承模式。而且,instanceof和isPrototypeOf( )也能够用于识别基于组合继承创建的对象。

缺点:无论什么情况下,都会调用两次超类型构造函数,一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。

原型式继承

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

ECMAScript通过新增Object.create( )方法规范化了原型式继承。

缺点:包含引用类型值的属性始终都会共享相应的值,就像使用原型模式一样。

寄生式继承

创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真的处理之后一样返回对象。

缺点:使用寄生式集成来为对象添加函数,会由于不能做到函数复用而降低效率

寄生组合式继承

透過借用建構子來繼承屬性,透過原型鏈的混成形式來繼承方法。基本想法是:不必為了指定子類型的原型而呼叫超烈性的建構函數,我們所需要的是超類型原型的一個副本而已。即使用寄生式繼承來繼承超類型的原型,然後再將結果指定給子類型的原型。

優點:效率高,值呼叫一次超型原型的建構函數,原型鏈還能維持不變,能正常使用instanceof和isPrototypeOf( )。是最理想的繼承範式。


參考文件:https://blog.csdn.net/xiaoerjun/article/details/54524167

               https://blog.csdn.net/xiaoerjun/article/details/54572191#o## /chenxianru1/article/details/78527533

             / 
https

##

以上是關於JavaScript的原型繼承的超詳細介紹的詳細內容。更多資訊請關注PHP中文網其他相關文章!

來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板
關於我們 免責聲明 Sitemap
PHP中文網:公益線上PHP培訓,幫助PHP學習者快速成長!