java编程进阶:彻底搞懂原型和原型链

2023年 7月 11日 29.2k 0

java编程进阶:彻底搞懂原型和原型链java编程进阶:彻底搞懂原型和原型链

一、原型Prototype

1. 概述

JavaScript 通过构造函数生成新对象,因此构造函数可以视为对象的模板。实例对象的属性和方法,可以定义在构造函数内部。

function Cat (name, color) {
  this.name = name;
  this.color = color;
}
var cat1 = new Cat('大毛', '白色');
cat1.name // '大毛'
cat1.color // '白色'

上面代码中,Cat函数是一个构造函数,函数内部定义了name属性和color属性,所有实例对象(上例是cat1)都会生成这两个属性,即这两个属性会定义在实例对象上面。通过构造函数为实例对象定义属性,虽然很方便,但是有一个缺点。

1.构造函数优点是,构造函数创建的属性和方法可以在实例之间共享。

2.缺点为如果实例之间有相同的方法,这个方法会在每个实例上创建一遍,这样显然会造成系统资源的浪费。

function Cat(name, color) {
  this.name = name;
  this.color = color;
  this.meow = function () {
    console.log('喵喵');
  };
}
var cat1 = new Cat('大毛', '白色');
var cat2 = new Cat('二毛', '黑色');
cat1.meow === cat2.meow// false

上面代码中,cat1和cat2是同一个构造函数的两个实例,它们都具有meow方法。由于meow方法是生成在每个实例对象上面,所以两个实例就生成了两次。也就是说,每新建一个实例,就会新建一个meow方法。这既没有必要,又浪费系统资源,因为所有meow方法都是同样的行为,完全应该共享。

这个问题的解决方法,就是 JavaScript 的原型对象(prototype)。

2. prototype的使用

JavaScript 继承机制的设计思想就是, 原型对象的所有属性和方法,都能被实例对象共享。也就是说,如果属性和方法定义在原型上,那么所有实例对象就能共享,不仅节省了内存,还体现了实例对象之间的联系。

下面,先看怎么为对象指定原型。JavaScript 规定,每个函数都有一个prototype属性,指向一个对象。

function f() {}
typeof f.prototype // "object"

上面代码中,函数f默认具有prototype属性,指向一个对象。

对于普通函数来说,该属性基本无用。但是,对于构造函数来说,生成实例的时候,该属性会自动成为实例对象的原型。

function Animal(name) {
    this.name = name;
}
//构造函数的prototype指向一个对象,称为原型对象
Animal.prototype.color = 'white';
// 创建两个实例对象
var cat1 = new Animal('大毛');
var cat2 = new Animal('二毛');
//实例对象可以可以共享原型对象上面的属性和方法
console.log(cat1.color); //white
console.log(cat2.color); //white

上面代码中,构造函数Animal的prototype属性,就是实例对象cat1和cat2的原型对象。原型对象上添加一个color属性,结果,实例对象都共享了该属性。

java编程进阶:彻底搞懂原型和原型链

原型对象的属性不是实例对象自身的属性。只要修改原型对象,变动就立刻会体现在所有实例对象上。

Animal.prototype.color = 'yellow';
cat1.color // "yellow"
Cat2.color // "yellow"

上面代码中,原型对象的color属性的值变为yellow,两个实例对象的color属性立刻跟着变了。这是因为实例对象其实没有color属性,都是读取原型对象的color属性。也就是说,当实例对象本身没有某个属性或方法的时候,它会到原型对象去寻找该属性或方法。这就是原型对象的特殊之处。

如果实例对象自身就有某个属性或方法,它就不会再去原型对象寻找这个属性或方法。

cat1.color = 'black';
cat1.color // 'black'
cat2.color // 'yellow'
Animal.prototype.color // 'yellow';

上面代码中,实例对象cat1的color属性改为black,就使得它不再去原型对象读取color属性,后者的值依然为yellow。

总结一下,原型对象的作用,就是定义所有实例对象共享的属性和方法。这也是它被称为原型对象的原因,而实例对象可以视作从原型对象衍生出来的子对象。

Animal.prototype.walk = function () {
    console.log(this.name + ' is walking');
};

上面代码中,Animal.prototype对象上面定义了一个walk方法,这个方法将可以在所有Animal实例对象上面调用。

3. constructor 属性

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

function P() {}
console.log(P.prototype.constructor === P); // true

由于constructor属性定义在prototype对象上面,意味着可以被所有实例对象继承。

function Person() {}
var p1 = new Person();
console.log(p1.constructor === Person); // true
console.log(p1.constructor === Person.prototype.constructor); // trure
console.log(p1.hasOwnProperty('constructor')); // false

上面代码中,p1是构造函数Person的实例对象,但是p1自身没有constructor属性,该属性其实是读取原型链上面的Person.prototype.constructor属性。

java编程进阶:彻底搞懂原型和原型链

constructor属性的作用是,可以得知某个实例对象,到底是哪一个构造函数产生的。

function F() {};
var f = new F();
console.log(f.constructor === F); // true
console.log(f.constructor === Function); // false

上面代码中,constructor属性确定了实例对象f的构造函数是F,而不是Function。

4.__proto__属性

__proto__属性是每一个对象以及函数都隐含的一个属性。__proto__属性指向的是创建他的构造函数的prototype。原型链就是通过这个属性构件的。

function Person() {}
var p1 = new Person();
console.log(p1.__proto__ === Person.prototype); //true
console.log(Person.prototype.constructor === Person); //true
console.log(p1.constructor === Person); //true

java编程进阶:彻底搞懂原型和原型链

原型也是一个对象,既然是对象就会有__proto__属性,该属性指向原型对象的原型。

java编程进阶:彻底搞懂原型和原型链

Object.prototype 的原型呢?Object.prototype.__proto__ 的值为 null ,也就是说 Object.prototype 没有原型。所以查找属性的时候查到 Object.prototype 就可以停止查找了。

function Person() {}
var p1 = new Person();
console.log(p1.__proto__); //指向Person的原型
console.log(p1.__proto__.__proto__); //指向Person的原型的原型--Object.prototype
console.log(p1.__proto__.__proto__.__proto__); //null
console.log(Object.prototype.__proto__); //null

java编程进阶:彻底搞懂原型和原型链

图中由相互关联的原型组成的链状结构就是原型链,也就是蓝色的这条线。

5.原型链

JavaScript 规定,所有对象都有自己的原型对象(prototype)。一方面,任何一个对象,都可以充当其他对象的原型;另一方面,由于原型对象也是对象,所以它也有自己的原型。因此,就会形成一个“原型链”(prototype chain):对象到原型,再到原型的原型……

如果一层层地上溯,所有对象的原型最终都可以上溯到Object.prototype,即Object构造函数的prototype属性。也就是说,所有对象都继承了Object.prototype的属性。

那么,Object.prototype对象有没有它的原型呢?回答是Object.prototype的原型是null。null没有任何属性和方法,也没有自己的原型。因此,原型链的尽头就是null。

读取对象的某个属性时,JavaScript 引擎先寻找对象本身的属性,如果找不到,就到它的原型去找,如果还是找不到,就到原型的原型去找。如果直到最顶层的Object.prototype还是找不到,则返回undefined。如果对象自身和它的原型,都定义了一个同名属性,那么优先读取对象自身的属性,这叫做“覆盖”(overriding)。

java编程进阶:彻底搞懂原型和原型链

6.总结

1)在JS里,万物皆对象。方法(Function)是对象,方法的原型(Function.prototype)是对象。因此,它们都会具有对象共有的特点。即:对象具有属性__proto__,可称为隐式原型,一个对象的隐式原型指向构造该对象的构造函数的原型,这也保证了实例能够访问在构造函数原型中定义的属性和方法。原型对象也有一个属性,叫做constructor,这个属性包含了一个指针,指回原构造函数(__proto__和constructor属性是对象所独有的)。

2)函数这个特殊的对象,除了和其他对象一样有上述__proto__属性之外,还有自己特有的属性——原型属性(prototype),这个属性是一个指针,指向一个对象,这个对象的用途就是包含所有实例共享的属性和方法(我们把这个对象叫做原型对象)(prototype属性是函数所独有的)

3)__proto__属性的作用就是当访问一个对象的属性时,如果该对象内部不存在这个属性,那么就会去它的__proto__属性所指向的那个对象(父对象)里找,一直找,直到__proto__属性的终点null,再往上找就相当于在null上取值,会报错。通过__proto__属性将对象连接起来的这条链路即我们所谓的原型链。

4)prototype属性的作用就是让该函数所实例化的对象们都可以找到公用的属性和方法。

5)constructor属性的含义就是指向该对象的构造函数。

相关文章

JavaScript2024新功能:Object.groupBy、正则表达式v标志
PHP trim 函数对多字节字符的使用和限制
新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
为React 19做准备:WordPress 6.6用户指南
如何删除WordPress中的所有评论

发布评论