2. js基础 - 原型和继承

原型与继承

为什么需要原型?

构造器创建对象的时候, 实际上会有成员重复
如果使用 构造器 this.方法名 = function …. 方式创建对象. 那么每一个对象对应的方法就会重复

    function Person( name ) {
        this.name = name;
        this.sayHello = function() {
            console.log( '你好,我是 ' + this.name );
        };
    }
    var p1 = new Person( 'Hello' );
    var p2 = new Person( 'JACK' );
    p1.sayHello();
    p2.sayHello();
    // 只要执行一次函数, 那么函数内部的所有内容, 都会被创建出一次
    // 由于函数 Person 创建对象的时候 会 同时生成 里面的 sayHello 方法
    // 因此使得每一个 Person 的对象, 都包含一个内容完全相同, 不同的 sayHello 方法
    console.log( p1.sayHello == p2.sayHello );  // false
    // 每一个由构造函数 Person 创建的对象都会包含 一个 独立的 独有的 sayHello 方法,
    // 所以内存消耗越来越大,会性能很差

解决的方法,让这个方法( 函数 )共享
( 1 ). 将函数写到外面, 那么 Person 在初始化对象的时候就不会再创建一个函数了.只需要将 外面的函数引用 交给对象即可.缺点:一个对象可能有 n 多方法. 如果将所有的东西 都放到外面, 与其他库冲突的几率就会变大. 所以不宜采取该方法

    var sayHello = function( ) {
        console.log( '你好,我是 ' + this.name );
    }
    function Person( name ) {
        this.name = name;
        this.sayHello = sayHello;
    }
    var p1 = new Person( 'Hello' );
    var p2 = new Person( 'JACK' );
    p1.sayHello();  //你好,我是 Hello
    p2.sayHello();  //你好,我是 JACK
    console.log( p1.sayHello == p2.sayHello );  // true;

( 2 ). 将所有的方法( 函数 )都绑定到一个对象中

    var tool = {
        sayHello: function() {
            console.log( '你好,我是 ' + this.name );
        },
        sleep: function() {},
        eat: function() {}
    }
    function Person( name ) {
        this.name = name;
        this.sayHello = tool.sayHello;
    }
    var p1 = new Person( 'Hello' ); //你好,我是 Hello
    var p2 = new Person( 'JACK' );  //你好,我是 JACK
    p1.sayHello();
    p2.sayHello();
    console.log( p1.sayHello == p2.sayHello );  // true;

(3)js 原生就支持 解决该问题的办法,每一个函数都有 一个属性 prototype,该prototype属性指向一个对象. 每一个函数都存在该对象.
(重点) 每一个由函数作为构造器创建的对象, 都会默认连接到该对象上.如果访问对象的方法, 而对象中没有定义, 就会在这个 构造函数.prototype表示的对象中去找. prototype 就是原型之意

    function Foo() {
        /* 只要函数存在 这个神秘的对象就存在了 */
        /* Foo.prototype 就是该对象的引用 */
    }
    Foo.prototype.sayHello = function() {
        console.log( 'js 面向对象高级 ' );
    };
    var p = new Foo();
    p.sayHello();   //js 面向对象高级

使用 prototype 改良代码,提高了效率,也不会对代码造成污染

    function Person( name ) {
        this.name = name;
    }
    Person.prototype.walk = function() {
        console.log(this.name + ' walk')
    };
    Person.prototype.sayHello = function() {
        console.log(this.name + ' say hello')
    };
    var p1 = new Person( 'Jepson' );
    var p2 = new Person( 'Peter' );
    p1.sayHello(); // Jepson say hello
    p2.sayHello(); // Peter say hello
    console.log( p1.sayHello === p2.sayHello ); // true;

原型属性 原型对象

针对 构造函数 而言, 原型就是 构造函数的 prototype 属性, 常常将其称为 原型属性.
针对 实例对象 而言, 原型就是 实例对象的 原型对象。

    function Person () {}   // 有了构造函数, 和 原型
    var p = new Person();   // 有了实例

如何使用原型对象

简单的说就是将 共享的方法 放到原型中, 而 独有数据与行为 放在当前对象里。
1. 通过对象动态的特性,直接给原型对象添加成员,Person.prototype 默认有 constructor 属性,指明对应构造函数
例: Person( name, age, gender, sayHello, eat, sleep )

    // 首先需要构造函数, 原则就是将方法放在 原型中
    function Person( name, age, gender ) {
        this.name = name;
        this.age = age;
        this.gender = gender;
    }
    /* 还需要添加方法 */
    /* 原型也是对象 */
    Person.prototype.sayHello = function() {
        console.log( '你好,我是' + this.name );
    };
    Person.prototype.eat = function() {
        console.log( this.name + "在吃饭" );
    };
    Person.prototype.sleep = function() {
        console.log( this.name + "在睡觉, 睡了 " + this.age + " 年了");
    };
    /* 直接给原型对象添加成员 */
    var p1 = new Person( 'lilei', 19, '男' );
    var p2 = new Person( 'meimei', 20, '女' );
    p1.sayHello();  //你好,我是lilei
    p1.eat();   // lilei在吃饭
    p1.sleep(); // lilei在睡觉, 睡了 19 年了
    console.log('-----------------------------');
    p2.sayHello();  // 你好,我是meimei
    p2.eat();   // meimei在吃饭
    p2.sleep(); // meimei在睡觉, 睡了 20 年了
  1. 直接替换原型对象, Person.prototype 的原有属性 constructor 被覆盖了,要手动添加 ( 注意: 手动的添加 constructor 属性, 表示对应的构造函数 )
    function Person( name, age, gender ) {
        this.name = name;
        this.age = age;
        this.gender = gender;
    }
    /* 直接替换原型对象 */
    Person.prototype = {
        constructor: Person,// 手动的添加 constructor 表示对应的构造函数
        sayHello : function() {
            console.log( '你好,我是' + this.name );
        },
        eat: function() {
            console.log( this.name + "在吃饭" );
        },
        sleep: function() {
            console.log( this.name + "在睡觉, 睡了 " + this.age + " 年了");
        }
    };
    var p1 = new Person( 'lilei', 19, '男' );
    var p2 = new Person( 'meimei', 20, '女' );
    p1.sayHello();  //你好,我是lilei
    p1.eat();   // lilei在吃饭
    p1.sleep(); // lilei在睡觉, 睡了 19 年了
    console.log('-----------------------------');
    p2.sayHello();  // 你好,我是meimei
    p2.eat();   // meimei在吃饭
    p2.sleep(); // meimei在睡觉, 睡了 20 年了

__proto__

  1. 和 prototype 是否存在关系?
    早期浏览器是不支持 __proto__
    火狐率先使用该属性,然后用了呢,觉得非常好,基本现在的新浏览器都支持该属性,
    但因为不标准,不兼容低版本浏览器,注意:IE8不支持。一般多用于测试
    作用: 利用__proto__可以快速的访问原型

        p1.__proto__ === Person.prototype; // true;
  2. 访问
    使用构造函数, 就使用 prototype 属性访问原型
    使用实例对象, 就使用 非标准的 proto 属性访问原型

使低版本浏览器支持__proto__

  1. 判断浏览器是否支持 proto
     if ( {}.__proto__ ) {
        console.log( '支持' );
     } else {
        console.log( '不支持' );
     }
  1. 为了模拟该用法, 不使用 __proto__ 去访问原型
    最保险的方式就是利用构造函数
    通过 instance.constructor 访问 instance对象属性,而instance对象属性中没有定义,会在Person.prototype中找,就找到了Person.prototype.constructor
    有个问题:万一construcotr被覆盖掉了,就会获取失败,所以按照规范,我们替换掉原型对象后,要添加上 constructor 属性
    function __getProto__( instance ) {
        return instance.constructor.prototype;
    }
    function Person() {}
    var p = new Person();
    console.log( __getProto__( p ) );
    console.log( __getProto__( p ) === p.__proto__ ); // true;

继承

什么是继承

  1. 什么是继承? 自己没有,别人有,拿过来自己用,就好像自己的一样。前面的原型基本上都是这样。
  2. 原型与实例对象:
    在 js 中, 方法定义在原型对象中, 而属性定义在实例对象中。调用方法的时候, 实例对象本身是没有该成员的, 但是依旧可以调用该方法, 好像这个方法就是该实例对象的一样. 因此, 我们称该实例对象继承自 原型对象。
  3. 任何一个实例, 都是继承自其原型对象的. 即 原型式继承
    function Person( name, age, gender ) {
        this.name = name;
    }
    /* 直接替换原型对象 */
    Person.prototype = {
        sayHello : function() {
            console.log( '你好,我是' + this.name );
        },
    };
    var p1 = new Person( 'lilei', 19, '男' );
    p1.sayHello();  //你好,我是lilei

为什么需要继承

  1. 从编程的发展:复用( 重复使用 )
    div 标签 是 标签对象,而 a 标签 也是标签对象
    这两个标签本身不是同一个标签,但是,他们都有 nodeName, nodeType…等
    所以我们就要思考,怎样用更少的代码实现更多的方法。
    我们就可以提供一个 baseElement,让所有的标签都继承于这个 baseElement,那我们每个标签就都有这些属性方法了!所以我们需要继承。

  2. 从 js 的效率来看
    共享特性
    复用(组件化开发)

传统的编程语言的面向对象操作,例如 C++,JAVA等

  1. 对象: 是具有方法和属性的逻辑单元
    在 js 中,函数是一个特殊的数据类型( 函数在js中是一等公民)
    js 对象就是键值对,值如果是数据,那么键值就是构成属性
    如果值是函数,那么就构成方法

  2. 创建方式
    类 -> 实例化 -> 对象( 实例 )

        class Person {
            public string name;
            public int age;
            public string gender;
            pulbic void sayHello() {
                // ....
            }
        }

    类,用来描述对象的结构,它就是一个模板

        Person p = new Person();    // 利用模板创建对象
        p.name; // 访问属性
        p.sayHello(); //调用方法

    传统的面向对象编程语言, 重点需要一个”模板”, 即 类( class )

  3. 传统的继承
    传统的继承是模板的继承。

        // Student 继承自 Person 类
        class Student : Person {
            // ...
        }
        Student s = new Student();
        // 注意此时 s 就可以调用 sayHello 方法了
        // 可以使用  name,age 和 gender 属性了
  4. 相关概念

   /* 相关概念
   类 class             模板                    构造函数, 类名就是构造函数名
   子类 subclass         派生的模板               原型设置为指定对象的构造函数
   实例 instance         某个类的对象          ...
   实例成员(实例方法, 实例属性)
   静态成员
   静态方法            ...                     直接绑定在函数上的方法
   静态属性            ...                     直接绑定在函数上的属性 */

   // js 代码
   function Person () {
       this.name = '黄帝';
       this.age = 0;
       this.gender = '男';
   }

   // 不叫子类, 只是一个 Student 类
   function Student() {
   }

   // 继承派生 成为子类
   Student.prototype = new Person();

   // 即完成派生
   var s = new Student();
   s.name // OK

属性访问原则(重点)

  1. 对象在调用方法或访问属性的时候, 首先在当前对象中查询. 如果有该成员使用并停止查找
  2. 如果没有该成员就在其原型对象中查找. 如果有该成员即使用, 并停止查找
  3. 如果还没有就到 该对象的 原型对象 的 原型对象中查找…
    a.__proto__.__proto__.__proto__.__proto__.__proto__.__proto__.__proto__;
    //  HTMLAnchorElement > HTMLElement > Element > ... > Object > null
  1. 最后会查到 Object.prototype 上. 如果还没有即 返回 undefined

        function Person ( name ) {
            this.name = name;
        }
        Person.prototype.name = '小芳';
        Person.prototype.age = '18';
    
        var p = new Person( '李雷' );
        console.log( p.name );  // 李雷       先找自己,找到了,不找了
        console.log( p.age );   // 18       自己没有,找到原型中的 18,找到了,不找了
        console.log( p.gender );// undefined  自己没有,原型也没有,往原型的原型找... 最后undefined

如果修改原型对象中的属性值会怎样

给当前对象的原型提供的属性赋值, 实际上是给当前对象添加了该属性的新成员
并不会修改运行对象中的成员.

    function Person() { }
    Person.prototype.name = 'person';

    var p1 = new Person();
    console.log( p1.name );   // person 当前对象的原型提供的属性

    p1.name = 'peter';
    console.log( p1.name );   // peter 添加一个 对象属性
    console.log( p1.__proto__.name );   // person 继承属性不变

混入(mix)

    var o1 = { name: '张三' };
    var o2 = { age: 19 };
    o2.name = o1.name;

利用简单赋值,可以将一个对象中的成员加到另一个对象中
混入使得 o2 具有了 age 和 o1 中的 name, 即将 o1 混入到 o2中
混入也是一种继承的方式

如何用代码实现混入

    // 考虑到需要执行,写函数
    // 由于将一个对象混入到另一个对象,所以有两个参数
    function __mix__( obj, obj1 ) {
        for( var k in obj1 ) {
            obj[ k ] = obj1[ k ];
        }
    }
    var o1 = { name:'Jepson', age: 18 };
    var o2 = { talking:'Hello' }
    __mix__( o1, o2 );

希望混入是自己的一个方法,将别人的东西加到我身上

    // __mix__( 源对象, 新对象 )
    // => 源对象.__mix__( 新对象 )
    var o = {
        __mix__: function( obj ) {
            // 将 obj 中 的成员 加到 当前对象的 this 上
            for ( var k in obj ) {
                this[ k ] = obj[ k ];
            }
        }
    };

一般在很多开发中,都是用 extend 名字, 叫扩展, 其实一样的,一般向外公开 extend

    // __mix__( 源对象, 新对象 )
    // => 源对象.__mix__( 新对象 )
    var o = {
        extend: function( obj ) {
            // 将 obj 中 的成员 加到 当前对象的 this 上
            for ( var k in obj ) {
                this[ k ] = obj[ k ];
            }
        }
    };

extend完善,实现多个函数混入

    var o = {
        extend: function( obj ) {
            // 将 obj 中的成员 加到 当前对象 this 上
            for ( var i = 0; i < arguments.length; i++ ) {
                for ( var k in arguments[ i ] ) {
                    this[ k ] = arguments[ i ][ k ];
                }
            }
        }
    }
    o.extend({
        name: '李四'
    }, {
        age: 19,
        gender: '男'
    }, {
        sayHello: function () {
            console.log( 'Hello JS' );
        }
    });

原型式继承

写一个构造函数,如果需要将其实力继承自某个特定的对象 o,那么只需要设置该构造函数的 prototype 顺序为 o 即可

    function Person {}
    var o = { ... }
    Person.prototype = o;   // 继承

混合式继承

混合就是将 多个对象的各个功能 混合到一起,加到构造函数的原型对象上,那么该构造函数创建的实例,就继承自多个对象了。

    // 本身没有 sayHello 方法
    function Person() {}
    Person.prototype.extend = function( obj ) {
        for ( var k in obj ) {
            this[ k ] = obj[ k ];
        }
    };
    var p = new Person();
    p.sayHello();

利用 extend 继承 把成员新加到 原型中, 本身构造函数没有这些功能,就会到原型中去找,找到了,就有了 sayHello 方法

    function Person() {}
    Person.prototype.extend = function( obj ) {
        for ( var k in obj ) {
            this[ k ] = obj[ k ];
        }
    };
    Person.prototype.extend( {
        sayHello: function() {
            console.log( '你好,我是新加的' );
        },
        sleep: function() {
            console.log( '睡觉' );
        }
    } );
    var p = new Person();
    p.sayHello();

原型链

  • 对象 -> 父对象 -> 父对象的父对象 -> … -> 原对象 -> ….
  • 链式结构( 原型链 )
  • 原型链不宜过深
  • 由于链越深,属性搜索越需要性能,所以一般少用深层次的链式继承关系
    一般使用时,链结构只有 3 级, 一般都是使用混入,在原型上加成员

ES5 中 Object.create

实现继承的快速方法
create 原理, 根据参数对象建一个新的实例对象,这个实例对象时以参数对象为原型对象的

    // 基本语法
    // var newObj = Object.create( oldObj ); // 以 oldObj 为原型对象新建实例
    var o = { name: '张三', age: 20, gender: '男' };
    var obj = Object.create( o );
    console.log( obj.__proto__ === o ); // true
    // ES3
    Object.create = function ( o ) {
        function F() {}
        F.prototype = o;
        return new F();
    };
    // ES5 中内置了 该方法

低版本兼容

    // 如果在低版本的浏览器中要使用该方法: 先判断有没有. 如果没有再加上
    if ( !object.create ) {
       Object.create = function( o ) {
           function F() {}
           F.prototype = o;
           return new F();
       }
    }

这是 5年前的写法, 他的思想是直接在内置对象上添加成员,在书写兼容代码时,一般写一个通用的函数或方法,在函数或方法判断是否具有,如果原生具有,直接使用系统自带的,如果没有,我们自己实现。

  function createWithObject ( obj ) {
      if ( Object.create ) {
          return Object.create( obj );
      } else {
          function F() {}
          F.prototype = obj;
          return new F();
      }
  }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值