JS面向对象之原型

面向对象之原型

为何要使用原型

因为 js 是解释执行的语言, 那么在代码中出现的函数与对象, 若是重复执行, 那么会建立多个副本, 消耗更多的内存, 从而下降性能java

传统构造函数的问题

function Foo( ... ){
        this.name = name;
        this.age = age;
        this.sayHello = function(){
            ...
        }
    }
  1. 因为对象是由 new Foo() 建立出来的, 所以每个对象在建立的时候, 函数 sayHello 都会被建立一次
  2. 每个对象都含有一个对立的, 不容的, 可是功能逻辑同样的函数, 应该将单独的方法抽取出来, 让全部的对象共享该方法
  3. 使用传统方法的定义方式会影响性能, 在代码中建立方法就是消耗性能, 好比说内存
  4. 能够考虑将方法所有放到外面, 可是可能存在安全隐患
    • 在开发中会引入各类框架和库, 自定义的成员越多, 出现命名冲突的概率越大
    • 在开发中可能会存在多个构造函数, 每个构造函数应该会有多种方法, 方法越多就越不容易维护
function sayHello(){}
    function Foo(){
        this.sayHello = sayHello;
    }
  1. 每个函数在建立的时候, 都会建立一个原型对象
  2. 任意一个对象都会默认连接到他的原型
    • 只要建立一个函数, 就会附带建立一个特殊的对象, 这个对象使用函数.prototype引用, 这个特殊的对象是这个函数的原型属性
    • 每个由这个函数做为构造函数建立的对象, 都会默认连接到这个对象上( proto )
    • 在该对象访问某一个成员( 方法或属性 ), 若是该对象中没有, 那么就会到这个原型对象中去查找
var f1 = new Foo();
    var f2 = new Foo();
    f1.sayHello();    // 若是 f1 没有 sayHello, 那么就会在 Foo.prototype 中去找
    f2.sayHello();    // 若是 f2 没有改方法, 那么就会在 Foo.prototype 中去找
  1. 由构造函数建立出来的多个对象会共享一个原型对象, 就是 构造函数.prototype
  2. 合理解决办法: 将共享的, 重复使用会多消耗性能的东西放到 构造函数.prototype 中, 那么全部由这个构造函数建立的对象就能够共享这些成员
function Foo() {}
    Foo.prototype.sayHello = function () {
        console.log( ... );
    };
    var f1 = new Foo();
    f1.sayHello();
    var f2 = new Foo();
    f2.sayHello();

    f1.sayHello === f2.sayHello

原型使用注意点

  1. 写 构造函数.prototype 的时候, 最好不要将属性也加到里面.
function Person() {}
    Person.prototype.name = '张三';
    var p = new Person();
  1. 赋值的错误
    • 若是是访问数据, 当前对象中若是没有该数据就到构造函数的原型属性中去找
    • 若是是写数据, 当对象中有该数据的时候, 就是修改值; 若是对象没有该数据, 那么就添加值
    function Person() {}
     Person.prototype.name = '张三';
     var p1 = new Person();
     var p2 = new Person();
     p1.name = '李四';
    
     console.log( p1.name );
     console.log( p2.name );

    原型相关概念

面向对象的相关概念

类 class : 在 js 中就是构造函数

* 在传统的面向对象语言中( c, java ), 使用一个叫作类的东西定义模板, 而后使用模板建立对象
* 在构造方法中也具备相似功能, 叫作类

实例( instance ) 与对象( Object )

* 实例通常是指摸一个构造函数建立出来的对象, 叫作 XXX 构造函数的实例对象
* 实例就是对象, 对象是一个泛称
* 实例与对象是一个近义词

键值对与属性和方法

* 在 js 中, 键值对的集合被叫作对象
* 若是值为数据( 非函数 ), 就称该键值对为属性 property
* 若是值为函数( 方法 ), 就称该键值对为方法 method

父类( 基类 )和子类( 派生类 )

* 传统的面向对象的语言中使用类来实现继承, 那么就有父类, 子类
* 父类又称为基类, 子类有称为派生类
* 在 js 中, 经常被称为扶对象, 子对象, 基对象, 派生对象

原型的相关概念

原型对象相对于构造函数被称为原型属性

* 原型对象就是构造函数的**原型属性**
* 简称原型

原型对象与构造函数所建立的实例对象也有联系

* 原型对象相对于构造函数建立的实例对象称为**原型对象**
* 简称原型

对象继承自原型

* 构造函数建立的实例对象 继承自 构造函数的原型属性( 构造函数.prototype )
* 构造函数建立的实例对象 继承自 该对象的原型对象
* 构造函数建立的实例对象与构造函数的原型属性表示的对象是两个不一样的对象
    * 原型中的成员, 能够直接被实例对象调用
    * 实例对象 "*含有*" 原型中的成员
    * 实例对象 继承自 原型
    * 这样的继承就是 "原型继承"

构造函数是什么

* 凡是对象都有构造函数
    * {}  => Object
    * []  => Array
    * /./ => Regexp
    * function...  => Function

如何使用原型

  1. 使用对象的动态特性
    • 构造函数.prototype.XXX = vvv;
  2. 直接替换
function Person() {}
    Person.prototype = {
        constructor: Person
    };

    // 拆解
    function Person() {}
    var o = {};
    o.costructor = Person; // 属性中就存储着函数的地址

    Person.prototype = o;

    Person = 123;

这两种使用方法的区别

  1. 原型指向发生了变化
  2. 构造函数所建立的对象所继承的原型不一样
  3. 新增的对象默认是没有 constructor 属性
    注意: 在使用替换的方式修改原型的时候, 通常都会添加 constructor 属性

proto

  1. 之前访问原型, 必须使用构造函数才能实现, 没法直接使用实例对象来访问原型
  2. firefox 最先引入了__proto__属性, 来表示使用实例对象引用原型, 早期是非标准的
  3. 经过__proto__属性能够容许使用实例对象直接访问原型
function Person() {}
    // 原型对象就是 Person.prototype
    // 那么只有使用 构造函数 才能够访问它
    var o = new Person();
    // 之前不能直接使用 o 来访问神秘对象
    // 如今有了 __proto__ 后
    // o.__proto__ 也能够直接访问原型对象( 两个下划线 )
    // 那么 o.__proto__ === Person.prototype
  1. 原型对象中默认都有一个属性 constructor , 构造器 , 将该原型和该原型的构造函数联系起来
  2. __proto__的做用
    • 访问原型
    • 在开发中除非特殊需求, 不建议使用实例对象去修改原型的成员, 该属性使用较少
    • 在调试过程当中比较方便, 能够轻易的访问原型, 从而查当作员
  3. 间接访问原型的方法
var o = new Person();
    o.constructor.prototype;
  1. 给实例对象继承自原型的属性进行赋值
function Foo() {}
    Foo.prototype.name = 'test';
    var o1 = new Foo();
    var o2 = new Foo();
    o1.name = '张三'; // 不是修改原型中的 name 而是本身增长了一个 name 属性
    console.log( o1.name + ', ' + o2.name );

继承

  1. 最简单的继承就是 将别的对象的属性加到这个对象身上, 那么这个对象就有这个成员了
  2. 利用原型也能够实现继承, 不须要在这个对象身上添加任何成员, 只要原型有, 实例对象也有
  3. 因此将属性和方法等成员利用 混入 的办法, 加到构造函数的原型上, 那么构造函数的实例就都具备这些属性和方法了

静态成员和实例成员

  1. 静态成员表示的是 静态方法 和 静态属性, 静态就是由 构造函数提供的
  2. 实例成员表示的是 实例方法 和 实例属性, 实例就是由 构造函数建立的实例对象
  3. 通常状况下, 工具方法都是由 静态成员 提供, 与实例对象有关的方法由 实例成员 表示

构造函数 与 原型 与 构造函数建立的实例对象 的关系

function Person(name){
           this.name = name;
   }
    var P1 = new Person('Jim');

属性搜索原则( 就近原则 )

  1. 所谓的属性搜索原则, 就是对象在访问属性与方法的时候, 首先在当前对象中查找
  2. 若是当前对象中存储在属性或方法, 中止查找, 直接使用该属性与方法
  3. 若是对象没有改为员, 那么再其原型对象中查找
  4. 若是原型对象含有该成员, 那么中止查找, 直接使用
  5. 若是原型尚未, 就到原型的原型中查找
  6. 如此往复, 直到直到 Object.prototype 尚未, 那么就返回 undefied.
  7. 若是是调用方法就包错, 该 xxxx 不是一个函数
相关文章
相关标签/搜索