zoukankan      html  css  js  c++  java
  • javascript中的原型和原型链(三)

    1. 图解原型链


    1.1 “铁三角关系”(重点)

    function Person() {};
    var p = new Person();
    

      

    这个图描述了构造函数,实例对象和原型三者之间的关系,是原型链的基础:
    (1)实例对象由构造函数new产生;
    (2)构造函数的原型属性实例对象的原型对象均指向原型
    (3)原型对象中有一个属性constructor指向对应的构造函数

    原型链:p --> Person.prototype
    描述:实例对象能够访问到 Person.prototype 中不同名的属性和方法
    验证:

    p instanceof Person; // true
      
    p.__proto__ === Person.prototype; // true
      
    Person.prototype.constructor === Person; // true

    1.2 以原型为构造函数

    原型链:p --> Person.prototype --> Object.prototype --> null
    描述:
    (1)由于构造函数的原型也是对象,因此:它也有原型对象,指向Object.__proto__
    (2)由于构造函数的原型的原型也是对象,因此:它也有原型对象,指向null(特例)
    验证:

    p instanceof Person; // true
    p instanceof Object; // true
     
    Person.prototype instanceof Object; // true

    1.3 深入研究,引出Function构造函数

    1、原型链1:见1.2中的原型

    2、原型链2:Person --> Function.prototype --> Object.prototype --> null
    描述:
    (1)构造函数Person作为实例对象时,Person = new Function()隐式调用,因此Person --> Function.prototype
    (2)由于Function.prototype也是对象,Function.prototype = new Object()隐式调用,因此Function.prototype --> Object.prototype
    验证:

    Person instanceof Function; // true
    Person instanceof Object; // true
    Function.prototype instanceof Object; // true

    3、原型链3:Function --> Function.prototype --> Object.prototype --> null
    描述:
    构造函数Function作为实例对象时,Function = new Function()隐式调用,因此Function --> Function.prototype

    Function 这条原型链是最为特殊的“铁三角关系”,理解Function = new Function()就非常好理解了
    验证:

    Function.__proto__ === Function.prototype; // true
    Function instanceof Function; // true
    Function instanceof Object; // true

    1.4 完整的原型链

    图中新增了Object = new Function()的逻辑

    验证:

    Object instanceof Function;// true

    几个结论:
    (1)对象都有原型对象,对象默认继承自其原型对象
    (2)所有的函数都是 Function 的实例
    (3)所有的原型链尾端都会指向Object.prototype

    下面提几个问题:
    (1)上图有几条原型链?分别列出来(上面已给出)
    (2)如何在代码层面验证原型链上的继承关系?(见第四节)
    (3)图中有几个“铁三角”关系?分别列出来

    2. 原型链改写(重点)


    当实例对象被创建时,其原型链就已经确定了,当其对应的原型属性指向改变时,也无法改变原型链
    function Person({name="小A", age=21}={}) {
      this.name = name;
      this.age = age;
    };
    
    // 情况1:在修改原型属性前实例化对象
    var p1 = new Person();
    
    // 添加原型属性(方法)
    Person.prototype.sayName = function() {
      console.log(this.name);
    }
    // Person.prototype.SayHi = function() {}
    
    // 情况2:在修改原型属性后实例化对象
    var p2 = new Person();
    
    p1.sayName(); // "小A"
    p2.sayName(); // "小A"

    实例对象p1和实例对象p2的原型链相同,为 p1(p2) --> Person.prototype --> Object.prototype
    => 由于是在原有原型对象上添加的方法,相当于对象的扩展,故两个实例对象均能执行该方法

     

    function Person({name="小A", age=21}={}) {
      this.name = name;
      this.age = age;
    };
    
    // 情况1:在修改原型属性前实例化对象
    var p1 = new Person();
    
    // 重写原型对象
    Person.prototype = {
      sayName: function() {
        console.log(this.name);
      }
    }
    
    // 情况2:在修改原型属性后实例化对象
    var p2 = new Person();
    
    p2.sayName(); // "小A"
    p1.sayName(); // p1.sayName is not a function

    重写原型对象的方式,会改变实例对象的原型链,如下图所示:

    但是,为什么p1的原型链没有变,而p2的原型链变了呢?

    当实例对象被创建时,其原型链就已经确定了,当其对应的原型属性指向改变时,也无法改变原型链
    原型链是以实例对象为核心的,不能被原型对象的改变而误导

    重写原型对象的方式会在原型链继承中经常使用到!!!

    3. 对象与函数(重点)


    看到这里,我们可能已经分不清函数与对象了,思考30秒,函数与对象是什么关系?

    官方定义: 在Javascript中,每一个函数实际上都是一个函数对象
    function fn() {};
    var obj = {};
    
    fn instanceof Object; // true
    fn instanceof Function; // true
    
    obj instanceof Object; // true
    obj instanceof Function; // false

    原型链解释:
    fn对应的原型链:fn --> Function.prototype --> Object.prototype
    obj对应的原型链:obj --> Object.prototype

    从函数的定义来说: 在javascript中一切函数实际都是函数对象,但对象不一定是函数

    Function instanceof Object; // true
    Object instanceof Function; // true
    
    Function instanceof Function; // true

    原型链解释:
    Function对应的原型链(Function作为实例对象):Function --> Function.prototype --> Object.prototype
    Object对应的原型链(Object作为实例对象):Object --> Function.prototype --> Object.prototype

    由于Function和Object都是构造函数,在内置对象中,均会调用new Function()的方法

    结论:
    (1)函数一定是对象,但是对象不一定是函数
    (2)对象都是由函数来创建的

    针对第一点,这两个原型链可验证:
    fn --> Function.prototype --> Object.prototype
    obj --> Object.prototype

    针对第二点,可这样验证:

    var obj = { a: 1, b: 2}
    var arr = [2, 'foo', false]
    
    // 实际过程
    var obj = new Object()
    obj.a = 1
    obj.b = 2
    
    var arr = new Array()
    arr[0] = 2
    arr[1] = 'foo'
    arr[2] = false
    
    //typeof Object === 'function'  
    //typeof Array === 'function 

    4. 几个定义


    4.1 原型的定义和作用

    function Person() {};
    var p = new Person();
    构造函数的prototype属性的值(Person.prototype),
    也可以说成通过调用构造函数而创建出来的那个实例对象的原型对象(p.__proto__)
    1. 只要是函数就有 prototype 属性,即函数的原型属性(由于对象是由函数创建的,因此对象也有prototype属性)
    2. 函数的原型属性也是对象(因此,这个对象也有对应的prototype属性)
    3. 由构造函数创建出来的对象会默认链接到其构造函数的这个属性上(constructor)
    4. 构造函数的 prototype 属性的作用是:实现数据共享(继承)

    4.2 几个术语

    实例对象中有一个属性叫 __proto__ ,它是非标准属性,指向构造函数的原型属性

    Person.prototype 构造函数的原型属性
    p.__proto__ 实例对象的原型对象

    构造函数的原型属性实例对象的原型对象是一个东西,只是从不同的角度访问原型

    5. 属性搜索原则和属性来源判断


    5.1 属性搜索原则(重点)

    当代码读取某个对象的某个属性时,都会执行一次搜索,目标是具有给定名字的属性。搜索先从对象实例本身开始,如果在实例中找到了具有给定名字的属性,则返回该属性的值;如果没有找到,则继续搜索指针指向的原型对象,在原型对象中查找具有给定名字的属性。如果在原型对象中找到了这个属性,则返回这个属性,如果没有找到,则继续在这个原型对象的原型对象中查找,直到找到这个属性,否则返回undefined

    简言之,沿着对象的原型链查找属性,返回最近的属性,这就是属性搜索原则

    function Person(){}
    
    Person.prototype.name = "Nicholas";
    Person.prototype.age = 29;
    Person.prototype.job = "Software Engineer";
    Person.prototype.sayName = function(){
      alert(this.name);
    };
    
    var person1 = new Person();
    var person2 = new Person();
    
    person1.name = "Greg";
    alert(person1.name); //"Greg" 来自实例
    alert(person2.name); //"Nicholas" 来自原型 

    同样的,这也是属性屏蔽的原则

    // 接着上面的例子
    delete person1.namel;
    alert(person1.name); // "Nicholas" 来自原型

    5.2 hasOwnProperty()方法与in操作符

    使用hasOwnProperty()方法可以检测一个属性是存在于实例中,还是在原型中,这个方法只在给定属性存在于对象实例中时,才会返回true
    function Person(){}
    
    Person.prototype.name = "Nicholas";
    Person.prototype.age = 29;
    Person.prototype.job = "Software Engineer";
    Person.prototype.sayName = function(){
      alert(this.name);
    };
    
    var person1 = new Person();
    var person2 = new Person();
    
    alert(person1.hasOwnProperty("name"));  //false
    
    person1.name = "Greg";
    alert(person1.name); //"Greg" 来自实例 
    alert(person1.hasOwnProperty("name")); //true
    
    alert(person2.name); //"Nicholas" 来自原型
    alert(person2.hasOwnProperty("name")); //false
    
    delete person1.name;
    alert(person1.name); //"Nicholas" 来自原型
    alert(person1.hasOwnProperty("name")); //false
    有两种方式使用in操作符:单独使用和在for-in循环中使用。在单独使用时,in操作符会在通过对象能够访问给定属性时返回true,无论该属性存在于实例中还是原型中

    因此,同时使用hasOwnProperty()和in操作符,就可以确定某个属性到底是存在于对象中还是存在于原型中

    function hasPrototypeProperty(object, name){
      return !object.hasOwnProperty(name) && (name in object);
    }

    顺便一提,由于in操作符会在整个原型链上查找属性,处于性能考虑,在使用for-in循环时,建议多加一层判别

    function Person(){}
    
    Person.prototype.name = "Nicholas";
    Person.prototype.age = 29;
    
    var p = new Person();
    p.sex = "fale";
    
    for(key in p) {
      console.log(key); // sex name age 
    }
    
    // 实际上,我们一般只是在查找实例中的属性
    for(key in p) {
      if(p.hasOwnProperty(key)) {
        console.log(key); // sex 屏蔽了原型中的属性
      }
    }

    5.3 instanceof操作符

    instanceof 用来判断一个构造函数的prototype属性所指向的对象是否存在另外一个要检测对象的原型链上

    更形象来说,对于 A instanceof B来说,它的判断规则是:沿着A的__proto__这条线来找,同时沿着B的prototype这条线来找,如果两条线能找到同一个引用,即同一个对象,那么就返回true。如果找到终点还未重合,则返回false。不理解没关系,下面会结合图例分析

    function Person() {}
    var p = new Person();
    console.log(p instanceof Object);//true
    console.log(p instanceof Person);//true 
  • 相关阅读:
    ORM查询相关的操作
    分享一些珍藏和网上搜集的一些接码平台
    DRF: serializers ModelSerializer的序列化中model在有外键的情况下显示name代替显示id的几种方式
    Django Rest framework中序列化A表时怎么获取B表的数据
    10步入门Django Rest framework后端接口框架
    Django Rest framework后端接口框架,常用的子类视图
    redis学习(九)——数据持久化
    Java8之lambda表达式
    Java多线程(九)—— interrupt()和线程终止方式
    redis学习(八)——redis应用场景
  • 原文地址:https://www.cnblogs.com/chenyablog/p/11277571.html
Copyright © 2011-2022 走看看