zoukankan      html  css  js  c++  java
  • 理解 JavaScript 对象原型、原型链如何工作、如何向 prototype 属性添加新的方法。

    JavaScript 常被描述为一种基于原型的语言 (prototype-based language)——每个对象拥有一个原型对象,对象以其原型为模板、从原型继承方法和属性。原型对象也可能拥有原型,并从中继承方法和属性,一层一层、以此类推(这里的你可能还是懵的状态,先别管其他的,就知道原型是一层一层的就可以)。这种关系常被称为原型链 (prototype chain),它解释了为何一个对象会拥有定义在其他对象中的属性和方法。

    也就是说,我们没有特意去给一个实例对象添加属性和方法,但是这个实例对象却有属性和方法,这是因为这个实例对象继承了定义在构造器函数(我们事先定义好的一个函数,这个函数中有属性和方法,prototype就是特殊的属性,因为特殊所以它叫原型)中的属性和方法以及prototype。

    那么这种继承是怎么实现的呢?实例对象和它的构造器之间建立一个链接(它是__proto__属性,是从构造函数的prototype属性派生的),之后通过上溯原型链,在构造器中找到这些属性和方法。

    理解对象的原型(可以通过Object.getPrototypeOf(obj)或者已被弃用的__proto__属性获得)与构造函数的prototype属性之间的区别是很重要的。前者是每个实例上都有的属性,后者是构造函数的属性。也就是说,Object.getPrototypeOf(new Foobar())Foobar.prototype指向着同一个对象。

    构造函数、实例、原型(对象)、原型链到底是什么?

    1、来看个例子:

    创建一个构造函数Person,实例化这个Person为person1:

    function Person(name, age) { // 构造函数
       this.name = name
        this.age = age
    };
    Person.prototype.sex = "male" // Person.prototype叫原型(对象)
    var person1 = new Person('zs', 18) // 实例
    
    console.log(person1.__proto__, person1.__proto__.__proto__)

    结果显示两个框中的值是一样的,就是说person1.__proto__继续上溯一层__proto__,会找到上层的__proto__,也就是 person1.__proto__.__proto__,这两个量指向的同一个对象。那么这个上溯的过程就称为原型链

    2、构造函数的prototype(原型)实例的__proto__的关系是怎么样的呢?

    function Person(name, age) { // 构造函数
       this.name = name
        this.age = age
    };
    Person.prototype.sex = "male"
    var person1 = new Person('zs', 18) // 实例
    
    console.log(person1.__proto__, Person.prototype)

    结果如上:person1的__proto__指向的对象,就是Person的prototype(原型)指向的对象,也就是原型(对象)

    3、现在我们看个查找原型链的路径,按原型链的路径找person1.valueOf()方法:

    function Person(name, age) {
      
     this. name = name
        this.age = age
      
    };
    Person.prototype.sex = "male"
    var person1 = new Person('zs', 18)
    
    console.log(person1.valueOf())

    结果:

    valueOf() 这个方法仅仅返回了被调用对象的值。在这个例子中发生了如下过程:

    • 浏览器首先检查,person1 对象是否具有可用的 valueOf() 方法。
    • 如果没有,则浏览器检查 person1 对象的原型对象(即 Person构造函数的prototype属性所指向的对象)是否具有可用的 valueof() 方法。
    • 如果也没有,则浏览器检查 Person() 构造函数的prototype属性所指向的对象的原型对象(这个例子指 Object构造函数的prototype属性所指向的对象)是否具有可用的 valueOf() 方法。这里有这个方法,于是该方法被调用。
    • 原型链一般往上回溯可以回溯到Object.prototype;Object.prototype的原型对象是null;而null的原型对象不存在!可以用Object.getPrototypeOf()方法检测一个对象的prototype
      console.log(Object.getPrototypeOf(Object.prototype)); // null

      来尝试获取一下null 和 undefined 的 prototype

      console.log(Object.getPrototypeOf(null));//报错 Cannot convert undefined or null to object


      注意:读取对象的某个属性时,js引擎会先在对象本身属性上寻找,如果找不到,那么去原型对象上找,一层一层往上"上溯",直至null.所以如果找寻某个不存在的属性,会将完整的原型链遍历一变,对性能影响较大。

     我们来验证一下是不是这么去上溯的:

    function Person(name, age) {
      
     this. name = name
        this.age = age
      
    };
    Person.prototype.sex = "male"
    var person1 = new Person('zs', 18)
    
    console.log(person1.__proto__, Person.prototype)

    结果:

    我可以得出结论,实例对象 person1的__proto__指向的就是person1的构造函数(Person)的prototype属性(Person的原型),这两个量指向的是同一个对象,在这个对象中第一层并没有valueOf()方法,所以就找不到,找不到就上溯,Person是从内置对象Object实例出来的(Person是内置对象Object的实例),因此就要继续使用Person的__proto__到Object的prototype中去找valueOf()方法。来看代码:

    function Person(name, age) {
      
     this. name = name
        this.age = age
      
    };
    Person.prototype.sex = "male"
    var person1 = new Person('zs', 18)
    
    console.log(person1.__proto__, Person.prototype, Object.prototype)

    结果:

    这个时候,Object的prototype中有valueOf()方法,找到了!执行并打印出person1的内容:

    JavaScript 中到处都是通过原型链继承的例子。比如,你可以尝试从 StringDateNumber和 Array 全局对象的原型中寻找方法和属性。它们都在原型上定义了一些方法,因此当你创建一个字符串时:

    var myString = 'This is my string.';

    myString 立即具有了一些有用的方法,如 split()indexOf()replace() 等。

    4、constructor是个什么东东??

    prototype原型(对象)有一个constructor属性,默认指向prototype所在的构造函数。

    function Person(name, age) { // 构造函数
       this.name = name
        this.age = age
    };
    Person.prototype.sex = "male"
    var person1 = new Person('zs', 18) // 实例
    
    console.log(Person.prototype.constructor === Person) // true

    这段代码证明了构造函数的原型中有个constructor属性,这个属性指向构造函数!是不是又懵了?让我带你飞起来,接着看。

    来个图吧,

    有了这个图,是不是清楚一些了?

    5、继承的实现:

    prototype 属性大概是 JavaScript 中最容易混淆的名称之一。你可能会认为,this 关键字指向当前对象的原型对象,其实不是(还记得么?原型对象是一个内部对象,应当使用 __proto__ 访问)。prototype 属性包含(指向)一个对象,你在这个对象中定义需要被继承的成员。

     再来看,原型对象的修改:

    function Person(name, age) {
      
     this. name = name
        this.age = age
      
    };
    
    Person.prototype.sex = "male"
    
    var person1 = new Person('zs', 18)
    
    Person.prototype.farewell = function() {
      console.log('this is farewell fn!');
    }
    
    person1.farewell()

    结果:

    这时,我们看到,farewell已经被继承到了person1上。也就是说,旧有对象实例的可用功能被自动更新了。这证明了先前描述的原型链模型。这种继承模型下,上游对象的方法会复制到下游的对象实例中;下游对象本身虽然没有定义这些方法,但浏览器会通过上溯原型链、从上游对象中找到它们。这种继承模型提供了一个强大而可扩展的功能系统。

     当然这个person1对象实例拥有了farewell方法,如果我们再实例化出来多个新的对象实例,那这些新的对象实例也都有farewell方法,这个时候如果farewell显示的一个人的姓名,那是不是所有对象实例中的姓名都一样了?但是现实中,不可能所有人都有一样的名字,不过还好,人可以按“姓”划成不同的类,所以我们可以把同一个姓的的这个属性,写在原型中,把名写在实例上,这样用一个构造函数,就能实例化出这个姓氏下的所有人的姓名:

    function Person(lastName, age) {
      
         this.lastName = lastName
        this.age = age
    };
    
    Person.prototype.firstName = 'wang'
    var person1 = new Person('wu', 18)
    var person2 = new Person('liu', 20)
    
    console.log(person1.fullName =  person1.firstName + person1.lastName)
    console.log(person2.fullName =  person2.firstName + person2.lastName)

    好。“姓”这个属性,已经被person1和person2都继承到了。是不是不用每次都定义“姓”这个属性了?省了不少事。

    所以我们通过构造函数生成实例化对象本质其实就是将构造函数的prototype属性赋值给实例对象的原型(对象)。

    6、instanceof ()判断对象是否为构造函数的实例

    function Person(name, age) { // 构造函数
       this.name = name
        this.age = age
    };
    Person.prototype.sex = "male"
    var person1 = new Person('zs', 18) // 实例
    
    console.log(person1 instanceof Person) // true : person1 是Person的实例
    console.log(Person.prototype.isPrototypeOf(person1)) // true : Person(构造函数)的原型是person1(实例)的原型,指向同一个对象

    7、Object.getPrototypeOf() 获取一个对象的原型对象

    function Person(name, age) { // 构造函数
       this.name = name
        this.age = age
    };
    Person.prototype.sex = "male"
    var person1 = new Person('zs', 18) // 实例
    
    console.log(Object.getPrototypeOf(Person))
    console.log(Object.getPrototypeOf(person1))

    8、Object.setPrototypeOf():第一个参数是现有对象,第二个是原型对象;返回一个新对象

    var a = {
        name:'apple',
        f:function(){
            console.log('this is a test function');
        }
    };
    var b = Object.setPrototypeOf({},a);
    //b本身是空对象,它的原型对象是a;b能调用a的属性和方法
    console.log(b);
    console.log(b.name);//apple
    b.f(); //this is a test function

    9、Object.create()使用场景

    有时候,我们不能拿到对象的构造函数,只能取到实例对象;比如如下生成的:

    var a = {
        name:'apple'
    };
    
    var b = Object.create(a);
    
    console.log(b); // {}
    console.log(b.name); //apple
    console.log(Object.getPrototypeOf(b)===a) // true b的原型是a

    10、Object.prototype.isPrototypeOf:判断是否是某个对象的原型对象

    console.log(Object.prototype.isPrototypeOf(Array)); // true

    水平有限,前端小学生,文章中或有不当之处,欢迎批评指正!

  • 相关阅读:
    PAT A1094 The Largest Generation (25 分)——树的bfs遍历
    PAT A1055 The World's Richest (25 分)——排序
    PAT A1052 Linked List Sorting (25 分)——链表,排序
    PAT A1076 Forwards on Weibo (30 分)——图的bfs
    辅导员
    辅导员面试
    C程序设计
    Excel VBA 基本概念
    Excel函数
    导入excel表的数据到数据库ssh
  • 原文地址:https://www.cnblogs.com/whq920729/p/10664301.html
Copyright © 2011-2022 走看看