Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

对象&继承 #2

Open
LuanMingyang opened this issue Aug 13, 2018 · 0 comments
Open

对象&继承 #2

LuanMingyang opened this issue Aug 13, 2018 · 0 comments

Comments

@LuanMingyang
Copy link
Owner

对象&继承

本文总结自红宝书

一、创建对象

1. 工厂模式

1.1 优缺点

无法识别对象类型

1.2 实现

function createPerson(name, age) {
    var o = new Object();
    o.name = name;
    o.age = age;
    o.sayName = function() {
        alert(this.name);
    }
    return o;
}
var person = createPerson('Luan', 20);

2. 构造函数模式

2.1 优缺点

解决了对象类型识别的问题;

不同实例上的同名函数是不相等的,每个方法都要在每个实例上重新创建一遍。

2.2 实现

function Person(name, age) {
    this.name = name;
    this.age = age;
    this.sayName = function() {
        alert(this.name);
    }
}
var person1 = new Person('Luan', 20);
var person2 = new Person('Wang', 21);
person1.constructor === Person; // true
person1 instanceof Object; // true
person1 instanceof Person; // true
person1.sayName === person2.sayName; // false

2.3 new 操作符

使用new操作符,会经历四个步骤:

  1. 创建一个新对象
  2. 将构造函数的作用域赋给新对象
  3. 执行构造函数中的代码(为新对象添加属性)
  4. 返回新对象

模拟实现 new:

function create() {
    // 创建一个空的对象
    let obj = new Object()
    // 获得构造函数
    let Con = [].shift.call(arguments)
    // 链接到原型
    obj.__proto__ = Con.prototype
    // 绑定 this,执行构造函数
    let result = Con.apply(obj, arguments)
    // 确保 new 出来的是个对象
    return typeof result === 'object' ? result : obj
}

3. 原型模式

3.1 优缺点

原型对象的属性和方法由所有实例共享。

省略了构造函数传参初始化参数,所有实例在默认情况下都将取得相同的属性值;

原型中的所有属性都是被实例共享的,包括引用类型,导致一个实例修改引用类型属性后,其他实例的值也会跟着改变。

3.2 实现

function Person() {
}
Person.protptype.name = 'Luan';
Person.prototype.age = 20;
Person.prototype.sayName = function() {
    alert(this.name);
}
var person = new Person();

3.3 原型

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

3.4 原型对象

在默认情况下,所有原型对象会自动获得一个constructor(构造函数)属性,该属性是一个指向prototype属性所在函数的指针。

Person.prototype.constructor === Person; // true

调用构造函数创建的实例,内部将包含一个[[Prototype]]指针(该指针在Firefox、Safari、Chrome中可以通过__propto__属性访问,其他实现中不可见),指向构造函数的原型对象。

注意:[[Prototype]]指针存在于实例与构造函数的原型对象之间,而不是存在于实例与构造函数之间。

person.__propto__ === Person.proptotype; // true

可以通过实例访问原型对象中的值,但不能重写。当为实例添加一个属性时,这个属性会屏蔽原型对象中保存的同名属性;换句话说,实例添加同名属性只会阻止访问原型对象中的同名属性,但不会修改那个属性。

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

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

3.5 更简单的原型语法

function Person() {
}
// 重写了prototype对象,此时constructor属性不再指向Person
Person.protptype = {
    // 手动绑定constructor属性使其指向Person
    // 但会导致它的[[Enumerable]]特性被设为true,默认为false
    // 可以通过Object.defineProperty()设置为false
    constructor: Person,
    name: 'Luan',
    age: 20,
    sayName: function() {
        alert(this.name);
    },
}
var person = new Person();

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

4.1 优缺点

创建自定义类型最常见的方式;

构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性。

4.2 实现

function Person(name, age) {
    this.name = name;
    this.age = age;
    this.friends = ['Ming', 'Yang'];
}
Person.protptype = {
    constructor: Person,
    sayName: function() {
        alert(this.name);
    },
}
var person1 = new Person('Luan', 20);
var person2 = new Person('Wang', 21);
person1.friends === person2.friends; // false
person1.sayName === person2.sayName; // true

5. 动态原型模式

function Person(name, age) {
    this.name = name;
    this.age = age;
    this.friends = ['Ming', 'Yang'];
    // 只有在初次调用构造函数时才会执行
    if (typeof this.sayName !== 'function') {
        Person.prototype.sayName = function() {
            alert(this.name);
        }
    }
}

6. 寄生构造函数模式

无法使用instanceof操作符确定对象类型,一般不使用

function Person(name, age) {
    var o = new Object();
    o.name = name;
    o.age = age;
    o.sayName = function() {
        alert(this.name);
    }
    return o;
}
var person = new Person('Luan', 20);

7. 稳妥构造函数模式

所谓稳妥对象指的是没有公共属性,其方法也不引用this的对象

无法使用instanceof操作符确定对象类型

function Person(name, age) {
    var o = new Object();
    o.sayName = function() {
        alert(name);
    }
    return o;
}
var person = Person('Luan', 20);
// 除了使用sayName方法外,没有别的办法访问其数据
person.sayName(); // 'Luan'

二、继承

继承主要依靠原型链来实现。

1. 原型链

1.1 优缺点

超类型的实例属性会变成子类的原型属性,而包含引用类型的原型属性会被所有实例共享。

没有办法在不影响所有对象实例的情况下,给超类型的构造函数传递参数。

1.2 实现

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();
instance.getSuperValue(); // true
// SubType的原型对象指向了SuperType的原型对象
// 而SuperType的原型对象的constructor指向SuperType
instance.constructor === SuperType; // true

所有引用类型都默认继承了Object,所有函数的默认原型都是Object的实例

给子类原型添加方法的代码一定要放在替换原型的语句之后。

2. 借用构造函数

方法都在构造函数中定义,没有实现函数复用;

超类型原型中定义的方法,对子类是不可见的。

function SuperType(name) {
    this.name = name;
}
function SubType(name, age) {
    // 继承了SuperType
    SuperType.call(this, name);
    this.age = age;
}

3. 组合继承

最常用的继承模式;

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

function SuperType(name) {
    this.name = name;
}
SuperType.prototype.sayName = function() {
    alert(this.name);
}
function SubType(name, age) {
    // 继承属性
    SuperType.call(this, name); // 第二次调用超类型构造函数
    this.age = age;
}
// 继承方法
SubType.prototype = new SuperType(); // 第一次调用超类型构造函数
SubType.prototype.sayAge = function() {
    alert(this.age);
}

第一次调用SuperType构造函数时,SubType.prototype会得到name属性,即SuperType的实例属性;第二次调用SuperType构造函数时,在新对象上创建了实例属性nameage,屏蔽了原型对象中的同名属性。

4. 原型式继承

借助原型基于已有的对象创建新的对象,相当于创建了已有对象的副本,引用类型属性也会共享。

function object(o) {
    function F() {}
    F.prototype = o;
    return new F();
}

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

5. 寄生式继承

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

函数不能复用。

function createAnother(o) {
    var clone = object(o);
    o.sayHi = function() {
        alert('Hi');
    };
    return clone;
}

6. 寄生组合式继承

最理想的继承范式。

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

寄生组合式继承,通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。本质上,就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。

function inheritPrototype(subType, superType) {
    var prototype = object(superType.prototype); // 创建对象
    prototype.constructor = subType; // 增强对象
    subType.prototype = prototype; // 指定对象
}

function SuperType(name) {
    this.name = name;
}
SuperType.prototype.sayName = function() {
    alert(this.name);
}
function SubType(name, age) {
    SuperType.call(this, name);
    this.age = age;
}
// 注意!!!
inheritPrototype(SubType, SuperType);

SubType.prototype.sayAge = function() {
    alert(this.age);
}

只调用了一次SuperType的构造函数,并因此避免了在SubType.prototype上创建不必要的、多余的属性。同时,原型链保持不变,能够正常使用instanceof

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant