属性类型 | 创建对象 | 继承

一.面向对象编程语言基本概念:

1. 类

①是什么?类就是对象的类型模板。

②不懂,举个例子?

  • 比如,定义一个Classmate类,来表示同学,类本身是一种类型,Classmate表示同学类型,但不表示任何具体的某个同学。
2.实例

①是什么?根据类创建的对象。

②老规矩:

  • 比如,根据Classmate类可以创建小明、小红、小李等多个实例,每个实例都表示一个具体的同学,他们全都属于Classmate类型。
3.面向对象编程语言的基本概念是:类和实例。但对于JavaScript而言,它并不区分类和实例的概念,而是通过原型(prototype)来实现面向对象编程。

①原型机制是什么?

  • 通过将对象A的原型指向对象B,使得对象A可以获取对象B中的属性或方法。
var Person = {
    name: 'yyc',
    height: 170,
    run: function(){
        console.log(this.name + ' is running.');
    }
};
var asan = {
    name: 'asan'
};
asan.__proto__ = Person;
asan.name;
>>>"asan"

asan.run();
>>>asan is running.

《属性类型 | 创建对象 | 继承》 prototype.png

②试着把对象asan的原型指向另一个对象:

var hobby = {
    read: function(){
        console.log(this.name + ' is reading.');
    }
};
var asan = {
    name: 'asan'
};
asan.__proto__ = hobby;
asan.read();
>>>asan is reading.

③换种优雅点的方式去改变一个对象的原型:

var Person = {
    name: 'yyc',
    height: 170,
    run: function(){
        console.log(this.name + ' is running.');
    }
};
function createPerson(name){
    //o.__proto__ === Person.Prototype
    var o = Object.create(Person);
    o.name = name;
    return o;
}
var asan = createPerson('asan');
asan.name;
>>>"asan"

asan.run();
>>>asan is running.

《属性类型 | 创建对象 | 继承》 asan.png

二.属性类型

  • ECMA-262在第五版定义了只有内部采用的特性(attribute)来描述属性(property)的各种特性。

  • 为了表示特性是内部值,将它们放在两对方括号内,例如:[[Prototype]]

1.数据类型

《属性类型 | 创建对象 | 继承》 数据属性.png

var o = {};
var descriptor = Object.getOwnPropertyDescriptor(o);
descriptor;
>>>undefined

var o = {
 name: 'yyc'
};
var descriptor = Object.getOwnPropertyDescriptor(o,'name');
descriptor.configurable;
>>>true

descriptor.enumerable;
>>>true

descriptor.writable;
>>>true

descriptor.value;
>>>"yyc"

①想要修改属性默认的特性,必须使用ECMAScript5的Object.defineProperty()方法。该方法接收三个参数:属性所在的对象、属性的名字和一个描述符对象。其中描述符对象的属性必须是:configurableenumerablewirtablevalue

Writable

var Person = {
    name:'yyc',
    age: 21,
    city: 'Shanghai'
};
Object.defineProperty(Person,'name',{
    writable: false,
});
Person.name;
>>>"yyc"

Person.name = 'Gerg';
Person.name;
>>>"yyc"

Configurable

var Person = {
    name:'yyc',
    age: 21,
    city: 'Shanghai'
};
Object.defineProperty(Person,'name',{
    configurable: false,
});
Person.name;
>>>"yyc"

delete Person.name;
>>>false

Person.name;
>>>"yyc"

Person.age;
>>>21

delete Person.age;
>>>true

Person.age;
>>>undefined

Enumerable

var Person = {
    name:'yyc',
    age: 21,
    city: 'Shanghai'
};
for(var i in Person){
    console.log(i);
}
>>>
name
age
city

var Person = {
    name:'yyc',
    age: 21,
    city: 'Shanghai'
};
Object.defineProperty(Person,'name',{
    enumerable:false
});
for(var i in Person){
    console.log(i);
}
>>>
age
city
2.访问器属性:设置一个属性的值会导致其他属性发生变化,”牵一发而动全身”。

《属性类型 | 创建对象 | 继承》 访问器属性.png

var age = {
    _year: 1996,
    nowAge: 0
};
Object.defineProperty(age,'year',{
    get: function(){
        return this._year;
    },
    set: function(newValue){
        if(newValue > 1996){
            this._year = newValue;
            this.nowAge += (newValue - 1996);
        }
    }
});
age.year = 2017;
age.nowAge;
>>>21
//还有种古老的方式
var age = {
    _year: 1996,
    nowAge: 0
};
age.__defineGetter__('year',function(){
    return this._year;
});
age.__defineSetter__('year',function(newValue){
    if(newValue > 1996){
        this._year = newValue;
        this.nowAge += newValue - 1996;
    }
});
age.year = 2017;
age.nowAge;
>>>21
3.定义多个属性
  • 使用 Object.defineProperties()方法,通过描述符一次定义多个属性。该方法接收两个对象参数:第一个对象是要添加或修改其属性的对象,第二个对象的属性和第一个对象中要添加或修改的属性一 一对应。
var book = {};
Object.defineProperties(book,{
    _year: {
        writable: true,
        value: 2004
    },
    edition: {
        writable: true,
        value: 1
    },
    year: {
        get: function(){
            return this._year;
        },
        set: function(newValue){
            if(newValue > 2004){
                this._year = newValue;
                this.edition += newValue -2004;
            }
        }
    }
});
4.读取属性的特性
var book = {};
Object.defineProperties(book,{
    _year: {
        writable: true,
        value: 2004
    },
    edition: {
        writable: true,
        value: 1
    },
    year: {
        get: function(){
            return this._year;
        },
        set: function(newValue){
            if(newValue > 2004){
                this._year = newValue;
                this.edition += newValue -2004;
            }
        }
    }
});
var descriptor1 = Object.getOwnPropertyDescriptor(book,'_year');
descriptor1.value;
>>>2004

descriptor1.configurable;
>>>false

descriptor1.enumerable;
>>>false

descriptor1.writable;
>>>true

var descriptor2 = Object.getOwnPropertyDescriptor(book,'year');
descriptor2.value;
>>>undefined

descriptor2.configurable;
>>>false

descriptor2.enumerable;
>>>false

descriptor2.get;
>>>
ƒ (){
            return this._year;
        }

descriptor2.set;
>>>
ƒ (newValue){
            if(newValue > 2004){
                this._year = newValue;
                this.edition += newValue -2004;
            }
        }

三. 创建对象

1.虽然Object构造函数和对象字面量都可以用来创建单个对象,但有个缺点:使用同一个接口创建很多对象,会产生大量重复的代码。
//Object构造函数
var person = new Object();
person.name = 'Gerg';
person.age = 21;
person.job = 'Student';
person.sayName = function(){
    console.log(this.name);
};
//对象字面量
var person = {
    name: 'Gerg',
    age: 21,
    job: 'Student',
    sayName: function(){
        console.log(this.name);
    }
};
2.工厂模式

①为了解决这个重复代码的问题,创建了工厂模式:

function createPerson(name,age,job){
    var o = new Object();
    o.name = name;
    o.age = age;
    o.job = job;
    o.sayName = function(){
        console.log(this.name);
    };
    return o;
}
var person1 = createPerson('Gerg',21,'Student');
var person2 = createPerson('Nicholas',29,'Software Engineer');

②工厂模式虽然解决了创建多个相似对象的问题,却没有解决对象识别的问题(即如何知道一个对象的类型):

function createPerson(name,age,job){
    var o = new Object();
    o.name = name;
    o.age = age;
    o.job = job;
    o.sayName = function(){
        console.log(this.name);
    };
    return o;
}
var person1 = createPerson('Gerg',21,'Student');
var person2 = createPerson('Nicholas',29,'Software Engineer');
Object.prototype.toString.call(person1);
>>>"[object Object]"

Object.prototype.toString.call(person2);
>>>"[object Object]"
3.构造函数模式

①为了解决上述问题,创建了构造函数模式:

function Person(name,age,job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = function(){
        console.log(this.name);
    };
}
var person1 = new Person('Gerg',21,'Student');
var person2 = new Person('Nicholas',29,'Software Engineer');
person1 instanceof Person;
>>>true

person1 instanceof Object;
>>>true

②构造函数包括原生构造函数(ArrayObject等)和自定义的构造函数(可自定义其属性和方法)。

③构造函数始终都应该以一个写字母开头;而普通函数则应该以一个小写字母开头。

④创建自定义的构造函数意味着将来可以将它的实例标识为一种特定的类型。

person1 instanceof Person;
>>>true

⑤构造函数和普通函数的区别?

//构造函数
function Person(name,age,job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = function(){
        console.log(this.name);
    };
}
var person = new Person('Gerg',21,'Student');
person.sayName();
>>>Gerg
//普通函数
function Person(name,age,job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = function(){
        console.log(this.name);
    };
}
Person('yyc',21,'Student');
//注意window喔
window.sayName();
>>>yyc
//在全局作用域中调用一个函数时,this对象总是执行Global对象(在浏览器中就是window对象)

⑥构造函数的缺点:

  • 每个方法都要在每个实例上重新创建一遍,同时,不同实例上的同名函数是不相等的。
person1.sayName === person2.sayName;
>>>false

可以将函数定义转移到构造函数的外面来解决这个问题:

function Person(name,age,job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = sayName;
}
function sayName(){
    console.log(this.name);
}
var person1 = new Person('Gerg',21,'Student');
var person2 = new Person('Nicholas',29,'Software Engineer');
person1.sayName === person2.sayName;
>>>true
person1.sayName();
>>>Gerg
  • 可是这样问题又来了:如果对象需要定义很多方法,那么就需要定义等量的全局函数,这样以来,我们自定义的引用类型就丝毫没有封装性而言了。

4.原型模式

4.1理解原型对象

function Person(){}
Person.prototype.name = 'Gerg';
Person.prototype.age = 21;
Person.prototype.job = 'Student';
Person.prototype.sayName = function(){
    console.log(this.name);
};
var person1 = new Person();
person1.name;
>>>"Gerg"

var person2 = new Person();
person2.name;
>>>"Gerg"

②虽然在所有实现中都无法访问到内部特性[[Prototype]],但可以通过isPrototypeOf()方法(原型对象的方法)来确定实例与构造函数的原型对象是否存在这种关系:

Person.prototype.isPrototypeOf(person1);
>>>true

Person.prototype.isPrototypeOf(person2);
>>>true

③ECMAScript5新增了一个更高端的方法来实现,叫Object.getPrototypeof()

Object.getPrototypeOf(person1);
>>>
{name: "Gerg", age: 21, job: "Student", sayName: ƒ, constructor: ƒ}

Object.getPrototypeOf(person1) == Person.prototype;
>>>true

《属性类型 | 创建对象 | 继承》 getPrototypeOf().png

④虽然可以通过对象实例访问保存在原型对象的值,但却不能通过对象实例person1重写原型对象Person Prototype的值:

function Person(){}
Person.prototype.name = 'Gerg';
Person.prototype.age = 21;
Person.prototype.job = 'Student';
Person.prototype.sayName = function(){
    console.log(this.name);
};
var person1 = new Person();
person1.name;
>>>"Gerg"

person1.prototype.name = 'yyc';
person1.name;
>>>Uncaught TypeError: Cannot set property 'name' of undefined

⑤如果我们在实例中添加了一个属性,不巧,该属性名与实例的原型对象中的一个属性同名,那么,当我们在实例中创建该属性时,该属性会屏蔽原型中的同名属性(搜索优先级):

function Person(){}
Person.prototype.name = 'Gerg';
Person.prototype.age = 21;
Person.prototype.job = 'Student';
Person.prototype.sayName = function(){
    console.log(this.name);
};
var person1 = new Person();
person1.name;
>>>"Gerg"

person1.name = 'yyc';
person1.name;
>>>"yyc"

⑥但是呢,如果使用delete操作符删除实例对象中的与其构造函数的原型对象同名的属性,那么它就不得不访问原型对象中的属性了:

function Person(){}
Person.prototype.name = 'Gerg';
Person.prototype.age = 21;
Person.prototype.job = 'Student';
Person.prototype.sayName = function(){
    console.log(this.name);
};
var person1 = new Person();
person1.name = 'yyc';
person1.name;
>>>"yyc"

delete person1.name;
person.name;
>>>"Gerg"
4.2原型和in操作符

①那么,怎么知道这个属性存在于实例中,还是原型中呢?这个时候hasOwnProperty()方法就登场了,如果该属性存在于实例中,返回true。

function Person(){}
Person.prototype.name = 'Gerg';
Person.prototype.age = 21;
Person.prototype.job = 'Student';
Person.prototype.sayName = function(){
    console.log(this.name);
};
var person1 = new Person();
person1.name;
>>>"Gerg"

//属性name存在于原型对象中
person1.hasOwnProperty('name');
>>>false

//属性name存在于实例对象中
person1.name = 'yyc';
person1.hasOwnProperty('name');
>>>true

②不过呢,hasOwnProperty()方法只能肯定一种情况,即当该属性存在于实例对象中时,返回true。但如果该对象存在于原型对象或压根就不存在时,均返回false

function Person(){}
Person.prototype.name = 'Gerg';
Person.prototype.age = 21;
Person.prototype.job = 'Student';
Person.prototype.sayName = function(){
    console.log(this.name);
};
var person1 = new Person();
person1.hasOwnProperty('gender');
>>>false

③还好我们可以单独使用in操作符,它的强大之处在于:该属性无论是存在于实例对象中,还是原型对象中均返回true,这样以来,通过hasOwnProperty()in操作符的组合就能判断该属性是否存在于原型对象中:

function Person(){}
Person.prototype.name = 'Gerg';
Person.prototype.age = 21;
Person.prototype.job = 'Student';
Person.prototype.sayName = function(){
    console.log(this.name);
};
function hasPrototypeProperty(object,name){
    return !object.hasOwnProperty(name) && (name in object);
}
var person1 = new Person();
//判断属性name是否存在于实例对象中
person1.hasOwnProperty('name');
>>>false

//判断属性name是否存在于原型对象中
hasPrototypeProperty(person1,'name');
>>>true

person1.name = 'yyc';
hasPrototypeProperty(person1,'name');
>>>false

④使用for...in循环时,返回的是能够通过对象访问的、可枚举的(enumerated)属性,其中既包括了实例中的属性,也包括了原型中的属性。屏蔽了原型中不可枚举属性(即把[[enumerable]]特性标记为false的属性)的实例属性也会在for..in循环中返回。

function Person(){}
Person.prototype.name = 'Gerg';
Person.prototype.age = 21;
Person.prototype.job = 'Student';
Person.prototype.sayName = function(){
    console.log(this.name);
};
var person1 = new Person();
for(var prop in person1){
    console.log(prop);
}
>>>
name
age
job
sayName

⑤我现在就想要实例对象中可枚举的所有属性,怎么办?

  • ECMAScript 5 提供了Object.keys()方法来解决这个问题,该方法接收一个对象作为参数,返回一个包含所有可枚举属性的字符串数组,该方法实例、原型对象都可以喔。
function Person(){}
Person.prototype.name = 'Gerg';
Person.prototype.age = 21;
Person.prototype.job = 'Student';
Person.prototype.sayName = function(){
    console.log(this.name);
};
var person1 = new Person();
var keys = Object.keys(person1);
keys;
>>>[]

function Person(){}
Person.prototype.name = 'Gerg';
Person.prototype.age = 21;
Person.prototype.job = 'Student';
Person.prototype.sayName = function(){
    console.log(this.name);
};
var person1 = new Person();
person1.name = 'yyc';
person1.age = 21;
var keys = Object.keys(person1);
keys;
>>>(2) ["name", "age"]
//这个方法很强大,还可以返回原型对象中的属性
function Person(){}
Person.prototype.name = 'Gerg';
Person.prototype.age = 21;
Person.prototype.job = 'Student';
Person.prototype.sayName = function(){
    console.log(this.name);
};
var keys = Object.keys(Person.prototype);
keys;
>>>(4) ["name", "age", "job", "sayName"]

⑥还有个小问题,我能不能获取实例对象或原型对象中的所有属性,包括不可枚举的?No problem,Object.getOwnPropertyNames()方法满足你的需要。

function Person(){}
Person.prototype.name = 'Gerg';
Person.prototype.age = 21;
Person.prototype.job = 'Student';
Person.prototype.sayName = function(){
    console.log(this.name);
};
var keys = Object.getOwnPropertyNames(Person.prototype);
keys;
//返回的结果包括了不可枚举的constructor属性
>>>(5) ["constructor", "name", "age", "job", "sayName"]
4.3更简单的原型语法

①每次添加一个属性或方法都要敲一遍Person.prototype,太麻烦, 可以用一个包含所有属性和方法的对象字面量解决这个问题:

function Person(){}
Person.prototype = {
    name: 'Gerg',
    age: 21,
    job: 'Student',
    sayName: function(){
        console.log(this.name);
    }
};
var person = new Person();
person.sayName();
>>>Gerg

②但这也造成了一个新的问题:constructor属性不再指向Person

function Person(){}
Person.prototype = {
    name: 'Gerg',
    age: 21,
    job: 'Student',
    sayName: function(){
        console.log(this.name);
    }
};
var friend = new Person();
friend.constructor === Person;
>>>false

friend.constructor === Object;
>>>true

③如果constructor属性真的重要,可以人工设置:

function Person(){}
Person.prototype = {
    constructor: Person,
    name: 'Gerg',
    age: 21,
    job: 'Student',
    sayName: function(){
        console.log(this.name);
    }
};
var friend = new Person();
friend.constructor === Person;
>>>true

④但是呢,又出现了新的问题:constructor属性变成了可枚举的了(enumerated

function Person(){}
Person.prototype = {
    constructor: Person,
    name: 'Gerg',
    age: 21,
    job: 'Student',
    sayName: function(){
        console.log(this.name);
    }
};
Object.keys(Person.prototype);
>>>(5) ["constructor", "name", "age", "job", "sayName"]

④试试Object.defineProperty()

function Person(){}
Person.prototype = {
    name: 'Gerg',
    age: 21,
    job: 'Student',
    sayName: function(){
        console.log(this.name);
    }
};
Object.defineProperty(Person.prototype,'constructor',{
    enumerable: false,
    value: Person
});
Object.keys(Person.prototype);
>>>(4) ["name", "age", "job", "sayName"]
4.4原型的动态性

①对于原型对象所做的任何修改都能立即从实例上反应出来,即使先创建了实例后修改原型也一样:

var friend = new Person();
Person.prototype.sayHi = function(){
    console.log('hi');
};
friend.sayHi();
>>>hi

②实例中的指针仅指向最初的原型对象,而不是指向构造函数。

function Person(){

}
var friend = new Person();
Person.prototype = {
    constructor: Person,
    name: 'Gerg',
    age: 21,
    job: 'Student',
    sayName: function(){
        console.log(this.name);
    }
};
friend.sayName();
//欢迎入坑
>>>Uncaught TypeError: friend.sayName is not a function

③这是为什么呢?

《属性类型 | 创建对象 | 继承》 重新原型对象.png

4.5原生对象的原型

①原型模式的重要性不仅体现在创建自定义类型方面,就连所有原生的引用类型(ArrayObjectString等等)都是采用这种模式创建的。所有原生引用类型都是在其构造函数的原型上定义了方法:

Object.prototype.toString.call(Array.prototype.sort);
>>>"[object Function]"

Object.prototype.toString.call(String.prototype.substring);
>>>"[object Function]"

②通过原生对象的原型,不仅可以取得所有默认方法的引用,同时可以定义新的方法:

String.prototype.startsWith = function(text){
    return this.indexOf(text) === 0;
};
var msg = 'Hello world.';
msg.startsWith('Hello');
>>>true

③为了防止不必要的命名冲突,上面的事情尽量别干。

4.6原型对象的问题

①最主要的问题就是共享性:你干的所有事,别人都知道,太可怕了。

  • 共享函数非常合适,共享包含基本值的属性也说得过去,但对于包含引用类型的属性来说,就出现问题了。
function Person(){

}
Person.prototype = {
    constructor: Person,
    name: 'Gerg',
    age: 21,
    job: 'Student',
    friends: ['asan','pangzi'],
    sayName: function(){
        console.log(this.name);
    },
}
var person1 = new Person();
var person2 = new Person();
person1.friends.push('qin');
person1.friends;
>>>(3) ["asan", "pangzi", "qin"]

person2.friends;
>>>(3) ["asan", "pangzi", "qin"]

person1.friends === person2.friends;
>>>true

5.怎么解决呢?组合构造函数模式和原型模式呗,男女搭配,构造函数模式负责实例,原型模式负责原型,还买一送一,能传参数。

function Person(name,age,job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.friends = ['asan','pangzi'];
}
Person.prototype = {
    constuctor: Person,
    sayName: function(){
        console.log(this.name);
    }
}
var person1 = new Person('Gerg',21,'Student');
var person2 = new Person('Nicholas',29,'Software Enginner');
person1.friends.push('qin');
person1.friends;
>>>(3) ["asan", "pangzi", "qin"]

person2.friends;
>>>(2) ["asan", "pangzi"]

person1.friends === person2.friends;
>>>false

person1.sayName === person2.sayName;
>>>true

6.动态原型模式

function Person(name,age,job){
    this.name = name;
    this.age = age;
    this.job = job;
    if(typeof this.sayName !== 'function'){
        Person.prototype.sayName = function(){
            console.log(this.name);
        };
    }
}
var friend = new Person('Gerg',21,'Student');
friend.sayName();
>>>Gerg

7.【不推荐】寄生构造函数模式,基本思想:创建一个函数,该函数的作用仅仅是封装创建对象的代码,然后再返回新创建的对象。返回的对象与构造函数或与构造函数的原型对象之间没有一毛钱关系。

function SpecialArray(){
    var values = new Array();
    values.push.apply(values,arguments);
    values.toPipedString = function(){
        return this.join('|');
    };
    return values;
}
var colors = new SpecialArray('red','green','blue');
colors.toPipedString();
>>>"red|green|blue"

8.稳妥构造函数模式:没有公共属性,不引用this对象,不使用new操作符调用构造函数。一切就是为了安全。返回的对象自然和构造函数和构造函数的原型对象没有关系。

function Person(name,age,job){
    var o = new Object();
    o.sayName = function(){
        console.log(name);
    }
    return o;
}
var friend = Person('Gerg',21,'Student');
friend.sayName();
>>>Gerg

四.继承

1.原型链(默认的原型Object Prototype)
function SuperType(){
    this.superproperty = true;
}
SuperType.prototype.getSuperValue = function(){
    return this.superproperty;
};
function SubType(){
    this.subproperty = false;
}
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function(){
    return this.superproperty;
};
var instance = new SubType();
console.log(instance.getSuperValue());
>>>true

《属性类型 | 创建对象 | 继承》 原型链.png

  • 让一个原型对象成为另一个类型的实例。
  • 特性[[Prototype]]是实例与构造函数的原型对象之间的纽带。
2.如何确定原型和实例的关系?

①第一种方法是:instanceof操作符。由于原型链的关系,可以说instanceObjectSuperTypeSubType中任何一个类型的实例:

function SuperType(){
    this.superproperty = true;
}
SuperType.prototype.getSuperValue = function(){
    return this.superproperty;
};
function SubType(){
    this.subproperty = false;
}
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function(){
    return this.subproperty;
};
var instance = new SubType();
instance instanceof SubType;
>>>true

instance instanceof SuperType;
>>>true

instance instanceof Object;
>>>true

②第二种方式是:isPrototypeOf()方法。

SubType.prototype.isPrototypeOf(instance);
>>>true

SuperType.prototype.isPrototypeOf(instance);
>>>true

Object.prototype.isPrototypeOf(instance);
>>>true
3.谨慎地定义方法

①通过原型链实现继承时,不能使用对象字面量创建原型方法。因为这样会重写原型链:

function SuperType(){
    this.superproperty = true;
}
SuperType.prototype.getSuperValue = function(){
    return this.superproperty;
};
function SubType(){
    this.subproperty = false;
}
SubType.prototype = new SuperType();
SubType.prototype = {
    getSubValue: function(){
        return this.subproperty;
    },
    someOtherMethod: function(){
        return false;
    }
};
var instance = new SubType();
instance.getSuperValue();
>>>Uncaught TypeError: instance.getSuperValue is not a function

instance instanceof SuperType;
>>>false

SuperType.prototype.isPrototypeOf(instance);
>>>false

SubType instanceof Object;
>>>true

SubType instanceof SuperType;
>>>false
4.原型链的问题

①原型链那么强大,有没有什么缺点?有!第一个缺点是:包含引用类型值的原型对象会使所有相应的实例共享该属性:

function SuperType(){
    this.colors = ['red','green','blue'];
}
function SubType(){

}
SubType.prototype = new SuperType();
var instance1 = new SubType();
instance1.colors.push('orange');
instance1.colors;
>>>(4) ["red", "green", "blue", "orange"]

var instance2 = new SubType();
instance2.colors;
>>>(4) ["red", "green", "blue", "orange"]

《属性类型 | 创建对象 | 继承》 原型链问题.png

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

5.那么如何解决原型对象中包含引用类型值所带来的问题?答案是:借用构造函数(constructor stealing)技术。其基本思想是:在子类型构造函数的内部调用超类型构造函数。
function SuperType(){
    this.colors = ['red','green','blue'];
}
function SubType(){
    SuperType.call(this);
}
var instance1 = new SubType();
instance1.colors.push('orange');
instance1.colors;
>>>(4) ["red", "green", "blue", "orange"]

var instance2 = new SubType();
instance2.colors;
>>>(3) ["red", "green", "blue"]

①SubType的每个实例都会具有自己的colors属性的副本。

②相对于原型链而言,借用构造函数还有个很大的优势:可以在子类型构造函数中向超类型构造函数传递参数:

function SuperType(name){
    this.name = name;
}
function SubType(){
    //继承了SuperType,还传递了参数
    SuperType.call(this,'Gerg');
    //实例属性
    this.age = 29;
}
var instance = new SubType();
instance.name;
>>>"Gerg"

instance.age;
>>>29

③但是单单用借用构造函数,就无法避免构造函数模式的问题:方法都在构造函数中定义。

6.组合继承:使用原型链实现对原型属性和方法的继承;使用借用构造函数实现对实例属性的继承:
function SuperType(name){
    this.name = name;
    this.colors = ['red','green','blue'];
}
SuperType.prototype.sayName = function(){
    console.log(this.name);
};
function SubType(name,age){
    //继承属性
    SuperType.call(this,name);
    //实例属性
    this.age = age;
}
//继承SuperType类型的实例属性和sayName()方法
SubType.prototype = new SuperType();
SubType.prototype.sayAge = function(){
    console.log(this.age);
};
var instance1 = new SubType('Gerg',21);
instance1.colors.push('orange');
instance1.colors;
>>>(4) ["red", "green", "blue", "orange"]

instance1.sayAge();
>>>21

instance1.sayName();
>>>Gerg

var instance2 = new SubType('asan',22);
instance2.colors;
>>>["red", "green", "blue"]

instance2.sayAge();
>>>22

instance2.sayName();
>>>asan
7.原型式继承:基于已有的对象person创建新的对象,同时还不必创建自定义类型。
function object(o){
    function F(){}
    F.prototype = o;
    return new F();
}
var person = {
    name: 'Gerg',
    friends: ['asna','qin','pangzi']
};
var anotherPerson = object(person);
anotherPerson.name = 'shoushan';
anotherPerson.friends.push('laowang');

var yetAnotherPerson = object(person);
yetAnotherPerson.name = 'guju';
yetAnotherPerson.friends.push('tuhao');
person.friends;
>>>(5) ["asna", "qin", "pangzi", "laowang", "tuhao"]

最后附个本文的方法整理

《属性类型 | 创建对象 | 继承》 方法整理.png

    原文作者:姚屹晨
    原文地址: https://www.jianshu.com/p/69a3d98b07e0
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞