美文网首页
JavaScript---原型

JavaScript---原型

作者: 昊啊昊儿哟 | 来源:发表于2018-06-05 20:00 被阅读13次

    学习目标

    • 使用 prototype 原型对象解决构造函数的问题
    • 理解什么是原型(原型对象)
    • 构造函数、prototype 原型对象、实例对象 三者之间的关系
    • 实例对象读写原型对象
    • 属性成员搜索原则:原型链
    • 原型对象的简写形式
    • 原生对象的原型
    • 原型对象的问题及使用建议

    构造函数的 prototype属性

    JavaScript 的每个对象都继承另一个父级对象,父级对象称为 原型(prototype)对象。

    原型也是一个对象,原型对象上的所有属性和方法,都能被子对象 (派生对象) 共享
    通过构造函数生成实例对象时,会自动为实例对象分配原型对象。
    而每一个构造函数都有一个prototype属性,这个属性就是实例对象的原型对象

    null没有自己的原型对象。

    这也就意味着,我们可以把所有对象实例需要共享的属性和方法直接定义在构造函数的 prototype 属性上,

    也就是实例对象的原型对象上。

    function Cat(color) {
      this.color = color;
    }
    
    Cat.prototype.name = "猫";
    Cat.prototype.sayhello = function(){
        console.log('hello'+this.name,this.color);
    }
    Cat.prototype.saycolor = function (){
        console.log('hello'+this.color);
    }
    
    var cat1 = new Cat('白色'); 
    var cat2 = new Cat('黑色'); 
    cat1.sayhello();
    cat2.saycolor();
    

    这时所有实例的 name 属性和 sayhello()saycolor 方法,
    其实都是同一个内存地址,指向构造函数的 prototype 属性,因此就提高了运行效率节省了内存空间。

    原型对象的获取及修改

    想要获取一个实例对象的原型对象,有两种方式:

    1:通过实例对象的构造函数的prototype属性获取: 实例对象.constructor.prototype

    2:通过实例对象的 _proto_ 属性获取: 实例对象._proto_

    而这两种方式,我们都不建议使用:

    obj.constructor.prototype在手动改变原型对象时,可能会失效。

    function P() {};
    var p1 = new P();
    
    function C() {};
    // 修改构造函数的prototype属性的值为p1
    C.prototype = p1; //也就是说,此后所有有C构造函数得到的对象的原型对象都是p1;
    
    var c1 = new C();
    
    console.log(c1.constructor.prototype === p1) // false
    

    推举设置获取实例对象的原型的方式

    Object.getPrototypeOf(实例对象) 方法返回一个对象的原型对象。

    这是获取原型对象的标准方法。

    function Cat(name, color) {
        this.name = name;
    }
    var cat1 = new Cat('猫'); //获取cat1对象的原型对象
    var s = Object.getPrototypeOf(cat1); 
    console.log(s);
    

    Object.setPrototypeOf(实例对象,原型对象) 为现有对象设置原型对象
    第一个是实例对象,第二个是要设置成为实例对象的原型对象的对象
    这是设置原型对象的标准方法。

    function Cat(name) {
        this.name = name;
    }
    var ob = {p:'波斯'};
    var cat1 = new Cat('猫'); 
    //设置cat1的原型对象为ob 
    Object.setPrototypeOf(cat1,ob); 
    console.log(cat1.p);//cat1的原型对象中有p属性 
    console.log(Object.getPrototypeOf(cat1));
    console.log(cat1.__proto__);
    //注意:如果对象的原型被改变,不会影响构造函数获取的原型的结果
    console.log(Cat.prototype == cat1.__proto__); //false
    

    以上的两中方法,都是在ES6新标准中添加的;

    原型及原型链

    所有对象都有原型对象;

    function Cat(name, color) {
        this.name = name;
     }
    
    var cat1 = new Cat('猫');
    
    console.log(cat1.__proto__.__proto__.__proto__);
    
    

    而原型对象中的属性和方法,都可以被实例对象直接使用;

    每当代码读取某个对象的某个属性时,都会执行一次搜索,目标是具有给定名字的属性

    • 搜索首先从对象实例本身开始

    • 如果在实例中找到了具有给定名字的属性,则返回该属性的值

    • 如果没有找到,则继续搜索指针指向的原型对象,在原型对象中查找具有给定名字的属性

    • 如果在原型对象中找到了这个属性,则返回该属性的值

    • 如果还是找不到,就到原型的原型去找,依次类推。

    • 如果直到最顶层的Object.prototype还是找不到,则返回undefined。

    而这正是多个对象实例共享原型所保存的属性和方法的基本原理。

    对象的属性和方法,有可能是定义在自身内,也有可能是定义在它的原型对象上。
    由于原型本身也是对象,又有自己的原型,所以形成了一条 原型链(prototype chain)。

    注意,不在要原型上形成多层链式查找,非常浪费资源

    更简单的原型语法

    前面例子中每添加一个属性和方法就要敲一遍 构造函数.prototype
    为减少不必要的输入,更常见的做法是用一个包含所有属性和方法的对象字面量来重写整个原型对象:

    function Person (name, age) {
      this.name = name
      this.age = age
    }
    
    Person.prototype = {
      type: 'human',
      sayHello: function () {
        console.log('我叫' + this.name + ',我今年' + this.age + '岁了')
      }
    }
    

    在该示例中,我们将 Person.prototype重置到了一个新的对象。
    这样做的好处就是为 Person.prototype 添加成员简单了,但是也会带来一个问题,那就是原型对象丢失了 constructor 成员(构造函数)。

    所以,我们为了保持 constructor的指向正确,建议的写法是:

    function Person (name, age) {
      this.name = name
      this.age = age
    }
    
    Person.prototype = {
      // 将这个对象的构造函数指向Person
      //constructor: Person, // => 手动将 constructor 指向正确的构造函数
      type: 'human',
      sayHello: function () {
        console.log('我叫' + this.name + ',我今年' + this.age + '岁了')
      }
    }
    
    var p = new Person();
    

    原生对象的原型

    所有构造函数都有prototype属性;

    • Object.prototype
    • Function.prototype
    • Array.prototype
    • String.prototype
    • Number.prototype
    • Date.prototype
    • ……

    为内置对象扩展原型方法:

    例:

    var ar = [1,5,23,15,5];
    
    function f(){
        var minarr = [];
        this.forEach(function(v,k){
            if(v<10){
                minarr.push(v);
            }
        })
        return minarr;
    }
    
    Object.getPrototypeOf(ar).min10 = f;
    console.log(ar.min10());//[1, 5, 5]
    
    // 其他数组对象也具有相应的方法
    var a = [1,2,34,7];
    console.log(a.min10()); //[1, 2, 7]
    

    这种技术被称为猴子补丁,并且会破坏封装。尽管一些流行的框架(如 Prototype.js)在使用该技术,但仍然没有足够好的理由使用附加的非标准方法来混入内置原型。

    原型对象的问题及使用建议

    性能问题:

    在原型链上查找属性时是比较消耗资源的,对性能有副作用,这在性能要求苛刻的情况下很重要。

    另外,试图访问不存在的属性时会遍历整个原型链

    //声明构造函数Man
    function Man(name){
        this.name = name;
        this.p = function(){
          console.log(this.name+'跑');
      }
    }
    
    var m = new Man('张三');
    
    console.log(m.hasOwnProperty('name')); // true
    console.log(m.hasOwnProperty('age')); //false
    

    hasOwnProperty是 JavaScript 中唯一处理属性并且不会遍历原型链的方法。

    注意:检查属性是否undefined还不够。该属性可能存在,但其值恰好设置为undefined

    //声明构造函数Man
    function Man(name){
        this.name = name;
        this.n = undefined;
        this.p = function(){
          console.log(this.name+'跑');
      }
    }
    
    var m = new Man('张三');
    
    if(m.n == undefined){
        console.log('没有n属性')
    }
    
    console.log(m.hasOwnProperty('name')); // true
    console.log(m.hasOwnProperty('name')); // true
    console.log(m.hasOwnProperty('n')); //true
    

    相关文章

      网友评论

          本文标题:JavaScript---原型

          本文链接:https://www.haomeiwen.com/subject/qrpmsftx.html