面向对象深度解析对象的继承

大纲

  1. 介绍对象
  2. 创建对象
  3. 原型链
  4. 区分prototype和__proto__
  5. 对象的继承

一、理解对象

1.1 什么是对象?

对象是无需属性的集合,其属性可以包含基本值、对象、或者函数

1.2 对象的属性?

对象的属性类型分为数据属性和访问器属性

1.2.1 数据属性
概念:

数据属性包含一个数据值的位置,在这个位置可以读取和写入值,数据属性有4个描述其行为的值

4个特性:

[[configurable]]:表示能否通过delete删除属性从而重新定义属性,能否修改属性的特性 默认值为true

[[Enumerable]]:表示能否通过for-in 来返回属性 默认true

[[writable]]:表示能否修改属性的值,默认true

[[value]]:包含这个属性的数据值

注意:Object.defineProperty() 创建新属性如果不指定,上述四个值都是false,如果是修改则不存在

例子:
        var person = {age:12,size:13};
        Object.defineProperty(person,'name',{
            writable:false,
            value:'dd',
        })
        Object.defineProperty(person,'age',{
            configurable:false,
            value:13,
        });
        Object.defineProperty(person,'size',{
            writable:false,
            value:60,
        });
        delete person.name;
        delete person.age;
        delete person.size;
        console.log(person);
结果:

1.2.2 访问器属性
概念:

访问器书不包含数据值,包含一对getter 和 setter函数,这两个函数不是必须。getter负责获取数据,setter负责写入数据

4个特性:

[[configurable]]:表示能否通过delete删除属性从而重新定义属性,能否修改属性的特性 默认值为true

[[Enumerable]]:表示能否通过for-in 来返回属性 默认true

[[Get]]:在读取数据时候调用

[[Set]]:在设置数据的时候调用

注意:访问器属性不能直接定义,必须使用Object.defineProperty() 来定义。一般创建对象内部只能通过函数来访问的表示需要增加下划线标识来区分

错误示例:
        var obj = {
            year:2012,
            moth:11,
        }
        Object.defineProperty(obj,'year',{
            get:function () {
                return this.year;
            },
            set:function (value) {
                this.year = value;
            }
        })
        console.log(obj);
正确示例:
var obj = {
    _year:2012,
    moth:11,
}
Object.defineProperty(obj,'year',{
    get:function () {
        return this._year;
    },
    set:function (value) {
        this._year = value;
    }
})
console.log(obj.year);
console.log(obj._year);
1.3 如何区分?

通过Object.getOwnPropertyDescriptor(obj,value);来获取属性来区分 

注意:原型的读写属性具有不对等效应。

二、创建对象

创建对象可以通过构造函数和字面量对象来创建,但是会产生大量的重复代码

2.1 字面量方式

var Person = {
 name:'Nike';
 age:29;  
}
2.2 工厂模式
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('Nike',29,'teacher');
var person2 = createPerson('Arvin',20,'student');
工厂模式 用函数来封装特定接口实现创建对象的细节  解决了创建多个相似对象的问题 却没有解决对象识别的问题
2.3 构造函数模式
function Person(name,age,job){
 this.name = name;
 this.age = age;
 this.job = job;
 this.sayName = function(){
 alert(this.name);
 }; 
}
var person1 = new Person('Nike',29,'teacher');
var person2 = new Person('Arvin',20,'student');

构造函数模式  没有显示的创建对象  直接将属性和方法赋给了this对象  没有return 语句 构造函数首字母必须大写 使用new操作符经历过程:

  1. 创建一个新对象   
  2. 将构造函数的作用域赋值给新对象  
  3. 执行构造函数中的代码  返回新对象     

构造函数也可以当做普通函数来定义 缺点:每个方法都要在实例上重新创建一遍 

2.4 原型模式
function Person(){}
Person.prototype.name = 'Nike';
Person.prototype.age = 20;
Person.prototype.jbo = 'teacher';
Person.prototype.sayName = function(){
 alert(this.name);
};
var person1 = new Person();
person1.sayName();
原型模式  我们创建的每一个函数都有一个prorotype (原型)属性。这个方法是一个指针 指向一个对象 这个对象的用途是包含特定类型的所有实例的共享方法
2.5 动态原型模式
function Person(name) {
    this.name = name;
    if(typeof this.sayName != "function"){
        Person.prototype.setName = function () {
            console.log(this.name);
        }
    }
}
var person1 = new Person();
  • 在构造函数中初始原型
  • 使用动态原型链的模式就不能通过对象字面量重写原型 
2.6 寄生构造函数模式
function Person = (name,age){
    var o = new Object();
    o.name = name;
    o.age = age;
    o.sayName = function () {
        console.log(this.name);
    }
    return o;
}
类似于工厂模式
2.7 稳妥构造函数模式
function Person = (name){
    var o = new Object();
    o.sayName = function () {
        console.log(name);
    }
    return o;
}

  • 没有公共属性,其方法也不引用this对象,稳妥对象最适合在一些安全环境中(禁止使用this和new)防止数据被其他程序应用
  • 除了暴露出来的方法没有其他方法来访问数据 
2.8 组合使用构造函数和原型模式
function Person(name,age,job){
 this.name =name;
 this.age = age;
 this.job = job;
}
Person.prototype = {
 constructor:Person,
 sayName: function(){
 alert(this.name);
 };
}
var person1 = new Person('Nike',20,'teacher');
  • 构造函数用于定义实例属性
  • 原型模式用于定义方法和共享属性
  • 最后每个实例都有自己的实例属性的副本,但是又共享对方法的引用最大程度节省内存 

三、对象继承

3.1 什么是原型链?

  • js主要依靠原型链来实现继承的
  • 基本思想是利用原型可以让一个引用类型继承另一个引用类型的属性和方法
  • 构造函数,原型和实例的关系是每个构造函数都有一个原型对象,原型对象都包含一个指针指向构造函数的指针 实例包含一个指向原型对象内部的指针
  • 如果原型对象等于另外一个实例 那么层层递进构成了实例和原型的链条  
  • 所有函数默认都是Object的实例 

如下图:


3.2 prototype和__proto__的区别?

几乎所有的函数(除了一些内建函数)都有一个名为prototype(原型)的属性,可称为显示原型这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以有特定类型的所有实例共享的属性和方法。prototype是通过调用构造函数而创建的那个对象实例的原型对象

对象具有属性__proto__,可称为隐式原型,一个对象的隐式原型指向构造该对象的构造函数的原型,这也保证了实例能够访问在构造函数原型中定义的属性和方法。

var parent = {name:'parent'}
var child = {name:'child'}
var grandson = {};
grandson.__proto__ = parent;
grandson.prototype = child;
console.dir(grandson);
console.log(grandson.name);

function Parent(name) {
    this.name = name;
}
Parent.prototype = {
    fn:function () {

    },
}
Parent.__proto__ = {
    test:function () {

    }
}
var a = new Parent('test');
console.dir(a);


给对象赋予prototype会称为一个属性的存在。给函数赋予__proto__ ,__proto__依旧展示的是prototype

3.3 继承的方式?

3.3.1 借用构造函数、伪造对象 或 经典继承
function SuperType() {
    this.colors = ['red','green'];
}
SuperType.prototype = {
    sayHi:function () {
        console.log('hi');
    }
}
function SubType() {
    SuperType.call(this);
}
var a = new SubType();
a.colors.push('black');
console.log(a.colors);
var b = new SubType();
console.log(b.colors);
console.dir(b);




  • 解决原型中包含引用类型所带来的问题 解决思路 在子类构造函数内部调用超类的构造函数
  • 使用apply 和call 方法在新创建的对象上执行构造函数
  • 借用构造函数问题:无法避免构造函数存在的问题。方法都在构造函数中定义 函数的复用无从谈起 
3.3.2 组合继承
function SuperType(name){
    this.name = name;
    this.colors = ['red','blue'];
}
SuperType.prototype.sayName = function () {
    console.log(this.name);
}

function SubType(name,age) {
    SuperType.call(this,name);
    this.age = age;
}
SubType.prototype = new SuperType();
SubType.prototype.sayAge = function () {
    console.log(this.age);
}
var person = new SubType('aa',12);
console.dir(person);


  • 是将原型链和借用构造函数的技术组合到一起发挥二者之长的一种模式
  • 背后的思路:使用原型链实现对原型属性和方法的继承 借用构造函数实现对实例属性的继承
  • 最常用的继承方式  这块的函数又问题 需要测试  
3.3.3 原型式继承
function object(o) {
    function F() {}
    F.prototype = o;
    return new F();
}

var person = {
    name:'aa',
    friends:['bb','cc']
}
var subPerson = object(person);
subPerson.name = 'ab';
subPerson.friends.push('dd');

var subPerson2 = object(person);
subPerson2.name = 'ac';
subPerson2.friends.push('cc');
console.log(subPerson2.friends);



  • 借助原型可以基于已有的对象创建新对象
  • ECMAScript5通过Object.create() 方法规范化了原型式继承 这个方法接受两个参数 一个用作新对象的的原型的对象 一个可选作为新对象定义额外属性的对象 

3.3.4 寄生式继承
function createPeople(original) {
    console.log(original);
    var clone = Object(original);
    clone.sayHi = function () {
        console.log('hi');
    }
    return clone;
}
function B() {
    this.b = 'b';
}
B.prototype = {
    sayHello:function () {
        console.log('hello')
    },
}
var a = new B();
a.sayHello();
var c = createPeople(a);
console.log(c);


  • 寄生式继承与原型式继承紧密相关的一种思路
  • 与寄生构造函数和工程模式类似  创建一个仅用于封装继承过程的函数 该函数在内部以某种方式来增强对象 
3.3.5 寄生组合式继承
function SuperType(name) {
    this.name = name;
    this.colors = ['red','blue','green']
}
SuperType.prototype.sayName = function () {
    console.log(this.name);
}
function inheritPrototype(subType,superType) {
    var prototype = Object(superType.prototype);
    prototype.constructor = subType;
    subType.prototype = prototype;
}
function SubType(name,age) {
    SuperType.call(this,name);
    this.age = age;
}
inheritPrototype(SubType,SuperType);
SubType.prototype.sayAge = function () {
    console.log(this.age);
}
var a = new SubType('aa');
console.log(a);
var c = new SuperType('aa');
console.log(c);



  • 组合继承是常见的继承模式 组合继承最大的问题是调用二次超类的构造函数,一次是创建子类原型 一次是子类构造函数内部
  • 寄生组合继承借用构造函数来继承属性 通过原型链混成形式来继承方法 背后的思路是不必为了指定子类型的原型而调用超类的构造函数 需要的无非是超类原型的一个副本
  • 意义:避免了在子类的prototype上创建不必要的,多余的属性 寄生组合继承是引用类型最理想的继承方式 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值