JavaScript의 프로토타입 상속에 대한 매우 자세한 소개

零到壹度
풀어 주다: 2018-04-13 17:20:37
원래의
1260명이 탐색했습니다.

이 글에서 공유한 내용은 특정 참조 값이 있는 JavaScript의 프로토타입 상속에 관한 것입니다. 도움이 필요한 친구는 이를 참조할 수 있습니다.

1. 객체 이해

ECMAScript에는 데이터 속성과 접근자 속성이라는 두 가지 속성이 있습니다.

1. 데이터 속성

데이터 속성에는 동작을 설명하는 4가지 특성(구성 가능, 열거 가능, 쓰기 가능, 값)이 있습니다.

구성 가능: 삭제를 통해 속성을 삭제하여 속성을 재정의할 수 있는지, 속성의 특성을 수정할 수 있는지, 접근자 속성으로 수정이 가능한지 여부를 나타냅니다. 객체에 직접 정의된 속성의 경우 기본값은 true입니다.
열거 가능: for-in 루프를 통해 속성을 반환할 수 있는지 여부를 나타냅니다.
 쓰기 가능: 속성 값을 수정할 수 있는지 여부를 나타냅니다.
값: 이 속성의 데이터 값을 포함합니다.
속성의 기본 속성을 수정하려면 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: 삭제를 통해 속성을 삭제하여 속성을 재정의할 수 있는지, 속성의 특성을 수정할 수 있는지, 접근자 속성으로 수정이 가능한지를 나타냅니다. 객체에 직접 정의된 속성의 경우 기본값은 true입니다.

열거 가능: for-in 루프를 통해 속성을 반환할 수 있는지 여부를 나타냅니다.

Get: 속성을 읽을 때 호출되는 함수입니다. 기본값은 정의되지 않습니다.

설정: 속성 작성 시 호출되는 함수입니다. 기본값은 정의되지 않았습니다.

접근자 속성은 직접 정의할 수 없으며 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와 setter를 동시에 지정할 필요는 없습니다. 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;
로그인 후 복사

객체와 배열은 모두 함수입니다.

console.log(typeof (Object));  // function
console.log(typeof (Array));  // function
로그인 후 복사

2. 객체 생성

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을 가리키는 생성자 속성이 있습니다.
요약: 사용자 정의 생성자를 생성한다는 것은 해당 인스턴스가 나중에 특정 유형으로 식별될 수 있음을 의미합니다. new 연산자를 통해 호출되는 모든 함수는 생성자로 사용할 수 있습니다. 새로움이 없으면 일반 기능과 다르지 않습니다.
생성자 문제: 생성자 사용의 주요 문제는 각 인스턴스에서 각 메서드를 다시 생성해야 한다는 것입니다.
함수 정의를 생성자 외부로 이동하면 이 문제를 해결할 수 있습니다.

 function Person(name, age, job){
            this.name = name;
            this.age = age;
            this.job = job;
            this.sayName = sayName;
        }
        function sayName(){
            alert(this.name);
        }
로그인 후 복사

그리고 이는 새로운 문제를 가져올 것입니다. 객체가 많은 메소드를 정의해야 한다면 많은 전역 함수를 정의해야 합니다. 이는 프로토타입 모드를 통해 해결할 수 있습니다.

3. 프로토타입 패턴

우리가 만드는 모든 함수에는 특정 유형의 모든 인스턴스에서 공유할 수 있는 속성과 메서드가 포함된 개체에 대한 포인터인 프로토타입 속성이 있습니다.
프로토타입 객체 사용의 장점: 생성자에서 객체 인스턴스 정보를 정의할 필요가 없으며 이 정보를 프로토타입 객체에 직접 추가할 수 있습니다.

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循环时,返回的是所有能够通过对象访问的、可枚举的属性,其中既包括存在于实例中的属性,也包括存在于原型中的属性。

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

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

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

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

사용자 정의를 만드는 가장 일반적인 방법이자 참조 유형을 정의하는 데 사용되는 기본 형식입니다.

인스턴스 속성은 생성자에서 정의되며, 인스턴스 생성자와 메서드가 공유하는 모든 속성은 프로토타입에서 정의됩니다.

장점: 집합 생성자와 프로토타입 패턴의 장점.

동적 프로토타입 패턴

초기화 후에 존재해야 하는 속성이나 메서드를 확인하려면 if 문을 사용하세요. 이 모드를 사용하여 생성된 객체의 유형은 instanceof 연산자를 사용하여 확인할 수 있습니다.

기생충 생성자 패턴

이 패턴은 new 연산자를 사용하고 생성자를 사용하여 래퍼 함수를 ​​호출한다는 점을 제외하면 팩토리 패턴과 동일합니다. 생성자가 값을 반환하지 않으면 기본적으로 새 객체 인스턴스를 반환합니다. 생성자 끝에 return 문을 추가하면 생성자를 호출하여 반환된 값을 재정의할 수 있습니다.

반환된 객체는 생성자 또는 생성자의 프로토타입 속성과 직접적인 관계가 없습니다.

instanceof 연산자를 사용하여 객체 유형을 결정할 수 없습니다.

다른 패턴을 사용해도 됩니다. 이 패턴은 사용하지 마세요.

안전한 생성자 패턴

안전한 개체: 공용 속성이 없고 해당 메서드가 이를 참조하지 않는 개체입니다.

기생 생성 패턴과의 두 가지 차이점: 첫째, 새로 생성된 객체의 인스턴스 메서드는 이를 참조하지 않으며, 둘째, 생성자를 호출하는 데 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

       /details/ 54572191

  |

위 내용은 JavaScript의 프로토타입 상속에 대한 매우 자세한 소개의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

관련 라벨:
원천:php.cn
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿
회사 소개 부인 성명 Sitemap
PHP 중국어 웹사이트:공공복지 온라인 PHP 교육,PHP 학습자의 빠른 성장을 도와주세요!