JS原型与原型链
1. 说明
JavaScript是面对对象编程,但是它又跟其他编程语言不一样,不同之处是JavaScript面对对象并不是依赖于抽象类,而是通过原型链。
在C++和Java使用new命令时,都会调用"类"的构造函数。而在Javascript语言中,new命令后面跟的不是类,而是构造函数。但是,用构造函数生成实例对象,有一个缺点,那就是无法共享属性和方法。为了解决这个问题,于是在构造函数设置了prototype属性,也就是原型。
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方法都是同样的行为,完全应该共享。这个问题的解决方法,就是JavaScript构造函数中添加prototype属性。
3. prototype是什么?
只有函数才有prototype属性,又称为显式原型。prototype属性包含一个对象,所有实例对象需要共享的属性和方法,都放在这个对象里面;那些不需要共享的属性和方法,就放在构造函数里面。
JavaScript规定,每个函数都有一个prototype属性,指向一个对象。
function f() {}
typeof f.prototype // "object"
对于普通函数来说,该属性基本无用。但是,对于构造函数来说,生成实例的时候,该属性会自动成为实例对象的原型。
function Animal(name) {
this.name = name;
}
Animal.prototype.color = 'white';
var cat1 = new Animal('大毛');
var cat2 = new Animal('二毛');
cat1.color // 'white'
cat2.color // 'white'
上面代码中,构造函数Animal的prototype属性,就是实例对象cat1和cat2的原型对象。原型对象上添加一个color属性,结果,实例对象都共享了该属性。
原型对象的属性不是实例对象自身的属性。只要修改原型对象,变动就立刻体现在所有实例对象上
Animal.prototype.color = 'yellow';
cat1.color // "yellow"
cat2.color // "yellow"
如果实例对象自身就有某个属性或方法,它就不会再去原型对象寻找这个属性或方法。
cat1.color = 'black';
cat1.color // 'black'
cat2.color // 'yellow'
Animal.prototype.color // 'yellow';
总结一下,原型对象的作用,就是定义所有实例对象共享的属性和方法。这也是它被称为原型对象的原因,而实例对象可以视作从原型对象衍生出来的子对象。
注:Object.prototype中Object是一个全局对象,也是一个构造函数,以及其他基本类型的全局对象也都是构造函数。
4. proto又是什么?
_proto_是每个对象都有的属性,又称为隐式原型。但是,_proto_不是一个规范属性,只是部分浏览器实现了此属性,对应的标准属性是[[Peototype]]。
大多情况下,proto_可以理解为‘构造函数的原型’,是实例和它的构造函数之间建立的链接,即_proto ===constructor.prototype
例子:
// 字面量方式
var a = {};
console.log(a.prototype); //undefined
console.log(a.__proto__); //Object {}
//构造器方式
var b = function(){}
console.log(b.prototype); //b {}
console.log(b.__proto__); //function() {}
// Object.create方式
var a1 = {}
var a2 = Object.create(a1)
console.log(b.prototype); //undefined
console.log(b.__proto__); //Object a1
ES6中的proto使用建议
本段摘自阮一峰ES6入门,具体解析可以点击查看。
1、_proto_属性没有写入ES6的正文,而是写入了附录,
2、原因是__proto__前后的双下划线,说明它本质上是一个内部属性,而不是一个正式的对外的API,只是由于浏览器广泛支持,才被加入了ES6。
3、标准明确规定,只有浏览器必须部署这个属性,其他运行环境不一定需要部署,而且新的代码最好认为这个属性是不存在的。
4、无论从语义的角度,还是从兼容性的角度,都不要使用这个属性,而是使用下面的Object.setPrototypeOf()(写操作)、Object.getPrototypeOf()(读操作)、Object.create()(生成操作)代替。
原型链是什么?
每个对象都会在其内部初始化一个属性,就是prototype(原型),当我们访问一个对象的属性时,如果这个对象内部不存在这个属性,那么他就会去prototype里找这个属性,这个prototype又会有自己的prototype,于是就这样一直找下去,也就是我们平时所说的原型链的概念。
原型链的终点
所有对象的原型最终都可以上溯到Object.prototype,
Object.prototype对象有没有它的原型呢?
回答是Object.prototype的原型是null。null没有任何属性和方法,也没有自己的原型。因此,原型链的尽头就是null。
1. 字符串原型链终点
let test = 'hello'
// 字符串原型
let stringPrototype = Object.getPrototype(test)
// 字符串的原型是String对象
stringPrototype === String.Prototype // true
// String对象的原型是Object对象
Object.getPrototypeOf(stringPrototype) === Object.prototype // true
2. 函数原理链的终点
let test = function () {}
let fnPrototype = Object.getPrototype(test)
fnPrototype === Function.prototype // true
Object.getPrototypeOf(Function.prototype) === Object.Prototype // true
原型链是用来做什么?
1. 属性查找
当JS引擎查找对象的属性时,先查找对象本身是否存在该属性,如果不存在,会去原型链上查找,但不会查找本自身的prototype。
用一个例子来形象说明一下:
let test = 'hello'
// 字符串原型
let stringPrototype = Object.getPrototype(test)
// 字符串的原型是String对象
stringPrototype === String.Prototype // true
// String对象的原型是Object对象
Object.getPrototypeOf(stringPrototype) === Object.prototype // true
2. 拒绝查找原型链
hasOwnPrototype:指示对象自身属性中是否具有指定的属性。
语法:obj.hasOwnPrototype(prop)。
参数prop:查找的属性。
返回值:Boolean判断是否存在。
let test = {'ABC': 'EDF' }
test.hasOwnPrototype('ABC') // true
test.hasOwnPrototype('toString') // false
该方法是在Object.prototype上,所有对象都可以使用,且会忽略掉那些从原型链上继承的属性。
扩展
constructor 属性
prototype对象有一个constructor属性,默认指向prototype对象所在的构造函数。
function P() {}
P.prototype.constructor === P // true
由于constructor属性定义在prototype对象上面,意味着可以被所有实例对象继承。
function P() {}
var p = new P();
p.constructor === P // true
p.constructor === P.prototype.constructor // true
p.hasOwnProperty('constructor') // false
上面代码中,p是构造函数P的实例对象,但是p自身没有constructor属性,该属性其实是读取原型链上面的P.prototype.constructor属性。
constructor 属性的作用
constructor属性的作用是,可以得知某个实例对象,到底是哪一个构造函数产生的。
举个小栗子:
function F() {};
var f = new F();
f.constructor === F // true
f.constructor === RegExp // false
另一方面,有了constructor属性,就可以从一个实例对象新建另一个实例。
再举个小栗子:
function Constr() {}
var x = new Constr();
var y = new x.constructor();
y instanceof Constr // true
注意点
原型:是一个对象与另一个对象相关联,则这个另一个对象就是原型,每个对象都从原型继承属性
proto:隐式原型,是每个对象都有的属性(除了null),注意proto左右两边是两条下划线
prototype:显式原型,是只有函数对象才有的属性(所以才用构造函数)
附录一:关系图
图中由相互关联的原型组成的链状结构就是原型链,也就是蓝色的这条线。
附录二:查找图
附录三:完整原型链图
附录四:测试代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JavaScript原型与原型链</title>
</head>
<body>
<script>
Person = function() {}; //创建一个构造函数,只有函数才有prototype属性
var person = new Person(); //通过构造函数创建一个实例对象,继承了构造函数的属性
//0层
person.age = 15;
console.log(person.age); //在该对象中找到了该属性,直接返回该值
//1层
Person.prototype.name = '小明'; //构造函数的prototype属性,即为实例原型
console.log(person.name); //输出小明 //打印的是实例对象的名字,即继承的构造函数的名字
//要找的是name属性,在person对象中未找到该属性,通过对象的__proto__属性找到它的原型,它的原型即是实例对象,并且原型中有该属性,返回该值
//2层
console.log(Person.prototype); //输出Object 输出的是它的原型 //打印的是实例原型,包含了小明这个属性
console.log(person.__proto__); //输出Object //通过person实例对象的_proto_属性指向实例原型,所以打印的是实例原型
console.log(Person.prototype === person.__proto__); //输出true 两个值都是实例原型
console.log(Person.prototype.__proto__); //输出构造函数,通过实例原型的__proto__属性指向原型对象,所以打印的是原型对象
console.log(Person.prototype.__proto__ === Object.prototype); //二者都为原型对象,所以输出为true
Object.prototype.name = '张三'; //原型对象的属性
console.log(Object.prototype.name);
console.log(person.name);
console.log(person.name === Object.prototype.name); //对象是从原型继承属性,二者的值不同,所以输出false
//3层
Object.prototype.text = '文本信息'; //
console.log(person.text); //输出文本信息
//在person对象中找不到text属性,在person对象的原型中也未找到该属性,通过原型的__proto__属性找到它的原型的原型,原型的原型即是原型对象,并且在原型的原型中找到该属性,返回该值
//4层
console.log(Object.prototype.__proto__); //输出null //原型对象没有原型,就打印为null
//再往上找原型对象的__proto__属性,即为null,未找到则返回undefined
</script>
</body>
</html>
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?