其实网上讲原型和原形链关系的文章有很多,现在只是做个笔记摘要,一开始看确实挺懵的,还是要多看几遍才行。
一、原型与原型链
原型:每一个javascript对象(除null外)创建的时候,就会与之关联另一个对象,这个对象就是我们所说的原型,每一个对象都会从原型中“继承”属性。原型是为了共享多个对象之间的一些共有特性(属性或方法)
原型链:通过隐式原型把一些构造函数层层的串起来,相互关联的原型组成的链状结构就是原型链。
每一个对象都可以通过proto隐形访问它的原型,当对象属性查不到时,就会去访问它的原型是否有这个属性,如果这个原型上没有想要的属性,那么就会去访问原型的原型,一直向上遍历,直到原型是null,就会返回undefined。
二、基本概念
先了解一下,什么是构造函数,什么是实例,什么是原型对象
function Person() {}
Person.prototype.name = 'name';
var person1 = new Person()
person1.name // 'name'
person1.__proto__ === Person.prototype // true
Person.prototype.constructor === Person // true
Person.prototype. __proto__ === Object.prototype // true
Person就是一个构造函数,我们使用new创建了一个对象person1,这个对象person1就是实例
函数都会自带 prototyoe 属性,它指向构造函数的原型对象,构造函数的prototype属性会自带constructor,指向它的构造函数
对象(函数也是对象)都会自带 _proto_ 属性,它指向构造函数的原型对象
可以得出一些结论:
- 构造函数Person自带prototype属性,它是一个对象,可以在上面定义属性和方法
- 构造函数的原型 Person.prototype自带constructor属性指向它的构造函数(Person)
- 函数也是一个对象,所以他也带有_proto_属性,指向它构造函数的实例(Object.prototype)
三、prototype
在JavaScript中,每个函数都有一个prototype属性,这个属性指向了一个对象,这个对象正是调用该构造函数而创建的实例的原型。可以理解为,原型对象是构造函数的一个实例。
原型的作用主要是继承,每个由构造函数创建的对象都会共享prototype上面的属性与方法。
构造函数和实例原型之间的关系:
function Person(age) {
this.age = age
}
function add(a,b) {
return a + b;
}
Person.prototype.name = 'test'
Person.prototype.add = add;
var person1 = new Person();
console.log(person1.name) // 'test'
console.log(person1.add(1,2)) // '3'
在这个例子中,person1 在创建时会去关联 Person的perototype属性,所以 Person.protptype 是person1的原型,person1可以拿到Person.prototype上定义的属性(name)和方法(add)
如果想重写原型,有些地方要注意
function Person(name) {
this.name = name
}
// 重写原型
Person.prototype = {
getName: function() {}
}
var p = new Person()
console.log(p.__proto__ === Person.prototype) // true
console.log(p.__proto__ === p.constructor.prototype) // false
console.log(p.constructor.prototype === Object.prototype) // true
这里直接重写了 Person.prototype,输出结果可以看出p.__proto__仍然指向的是Person.prototype,而不是p.constructor.prototype。
给Person.prototype赋值的是一个对象直接量{getName: function(){}},使用对象直接量方式定义的对象其构造器(constructor)指向的是根构造器Object,所以Person.prototype的constructor 是 Object
这时候需要给他设置一个构造函数 Person
function Person(name) {
this.name = name
}
// 重写原型
Person.prototype = {
constructor: Person,
getName: function() {}
}
var p = new Person()
console.log(p.__proto__ === Person.prototype) // true
console.log(p.__proto__ === p.constructor.prototype) // true
console.log(p.constructor.prototype === Object.prototype) // false
四、_proto_
这是每个对象(除null外)都会有的属性,叫做__proto__,这个属性会指向创建它的构造函数的原型对象。
构造函数和实例原型和实例之间的关系
可以看出,构造函数和实例对象都可以指向原型。
function Person() { }
var person1 = new Person();
console.log(person1.__proto__ === Person.prototype); // true
对象 person1 有一个 __proto__属性,创建它的构造函数是 Person,构造函数的原型对象是 Person.prototype 。
要明确的真正重要的一点就是,这个连接存在于实例(person1)与构造函数(Person)的原型对象(Person.prototype)之间,而不是存在于实例(person1)与构造函数(Person)之间。
实例可以通_proto_访问原型,构造函数可以通过prototype访问原型,那么原型有没有办法访问构造函数和实例呢?
原型指向实例倒是没有,因为一个构造函数可以生成多个实例。但是原型指向构造函数可以通过constructor属性 实现。
五、constructor
每个原型都有一个constructor属性,指向该关联的构造函数。
每个实例的construct属性,指向该实例的构造函数
构造函数和实例原型和实例之间的关系
function Person() {}
var person = new Person();
console.log(person.__proto__ == Person.prototype) // true
console.log(Person.prototype.constructor == Person) // true
console.log(person.constructor === Person); // true
当获取 person.constructor 时,其实 person 中并没有 constructor 属性,当不能读取到constructor 属性时,会从 person 的原型也就是 Person.prototype 中读取,正好原型中有该属性
person.constructor === Person.prototype.constructor
六、几个特殊的概念
所有函数对象的proto都指向Function.prototype,它是一个空函数(Empty function)
原型对象其实就是普通对象,但是也有一个例外,
Function.prototype,它的原型是一个函数 Function,而且它的原型没有prototype属性(前面说道函数都有prototype属性)
Number.__proto__ === Function.prototype // true
Number.constructor == Function //true
Number.prototype.__proto__ === Object.prototype // true
Boolean.__proto__ === Function.prototype // true
Boolean.constructor == Function //true
Boolean.prototype.__proto__ === Object.prototype // true
String.__proto__ === Function.prototype // true
String.constructor == Function //true
String.prototype.__proto__ === Object.prototype // true
// 所有的构造器都来自于Function.prototype,甚至包括根构造器Object及Function自身
Object.__proto__ === Function.prototype // true
Object.constructor == Function // true
String.prototype.__proto__ === null // true
// 所有的构造器都来自于Function.prototype,甚至包括根构造器Object及Function自身
Function.__proto__ === Function.prototype // true
Function.constructor == Function //true
Function.prototype.__proto__ === Object.prototype // true
Array.__proto__ === Function.prototype // true
Array.constructor == Function //true
Array.prototype.__proto__ === Object.prototype // true
RegExp.__proto__ === Function.prototype // true
RegExp.constructor == Function //true
RegExp.prototype.__proto__ === Object.prototype // true
Error.__proto__ === Function.prototype // true
Error.constructor == Function //true
Error.prototype.__proto__ === Object.prototype // true
Date.__proto__ === Function.prototype // true
Date.constructor == Function //true
Date.prototype.__proto__ === Object.prototype // true
JavaScript中有内置(build-in)构造器/对象共计12个(ES5中新加了JSON),这里列举了可访问的8个构造器。剩下如Global不能直接访问,Arguments仅在函数调用时由JS引擎创建,Math,JSON是以对象形式存在的,无需new。它们的proto是Object.prototype。
Math.__proto__ === Object.prototype // true
Math.construrctor == Object // true
JSON.__proto__ === Object.prototype // true
JSON.construrctor == Object //true
所有的构造器都来自于 Function.prototype,甚至包括根构造器Object及Function自身。所有构造器都继承了·Function.prototype·的属性及方法。如length、call、apply、bind
构造函数的原型都是对象,除了 Function.prototype
console.log(typeof Function.prototype) // function
console.log(typeof Object.prototype) // object
console.log(typeof Number.prototype) // object
console.log(typeof Boolean.prototype) // object
console.log(typeof String.prototype) // object
console.log(typeof Array.prototype) // object
console.log(typeof RegExp.prototype) // object
console.log(typeof Error.prototype) // object
console.log(typeof Date.prototype) // object
console.log(typeof Object.prototype) // object
七、一些题目
- person1.proto 是什么?
- Person.proto 是什么?
- Person.prototype.proto 是什么?
- Object.proto 是什么?
- Object.prototype__proto__ 是什么?
第一题,不用过多解释,答案是
person1.__proto__ === Person.prototype
第二题,Person 是个函数,所以它的构造函数是Function
Person.__proto__ === Function.prototype
第三题,Person.prototype 是个对象,对象的构造函数是Object
Person.prototype.__proto__ === Object.prototype
第四题,和第二题类似,因为Object本身是个函数,所以它的构造函数是Function
Object.__proto__ === Function.prototype
第五题,为了避免闭环,所以将Object原型的实例特殊处理为null
Object.prototype.__proto__ === null
八、Object.create()
通过Object.create()创建的对象实际上等于将该对象的__proto__指向Object.create()里面的参数对象
var a = {
name: 'sillywa'
}
var b = Object.create(a)
b.__proto__ === Object.prototype // false
b.__proto__ === a // true
b.__proto__.constructor === Object // true
b.__proto__.hasOwnProperty('constructor') // false
九、判断是否是原型
javascript有以下几种方法检测一个对象的原型:
- isPrototypeOf():检测一个对象是否是另一个对象的原型
- Object.getPrototypeOf(): 获得对象的直接原型。
- instanceof: 检测 constructor.prototype 是否存在于参数 object 的原型链上
object instanceof constructor
- obj.constructor.prototype:检测非Object.create()创建的对象的原型
其他
- hasOwnProperty:它能判断一个对象是否包含自定义属性而不是原型链上的属性,它是javascript中唯一一个处理属性但是不查找原型链的函数。
function Parent() {
this.name = 'parent';
this.age = 18;
}
function Child() {
this.name = 'child';
}
Child.prototype = new Parent();
var person1 = new Child();
var person2 = Object.creat(person1)
// isPrototypeOf()
Child.prototype.isPrototypeOf(person1) // true
Parent.prototype.isPrototypeOf(person1) // true
Object.prototype.isPrototypeOf(person1) // true
person1.isPrototypeOf(person2) // true
// getPrototypeOf()
Object.getPrototypeOf(person1) === Person.prototype // false
Object.getPrototypeOf(person1) === Child.prototype // true
Object.getPrototypeOf(person2) === person1 // true
// instanceof
person1 instanceof Parent // true
person1 instanceof Child // true
person2 instanceof Child // true
person2 instanceof person1 // ReferenceError person1 是对象,没有 prototype
// obj.constructor.prototype
person1.constructor.prototype === Parent.prototype // true
person1.constructor.prototype === Child.prototype // false
person2.constructor.prototype === Parent.prototype // true
person2.constructor.prototype === person1 // false
// hasOwnProperty
person1.hasOwnProperty('age'); // false
person1.hasOwnProperty('name'); // true
// 自定义的 testIsPrototype 检测函数
function testIsPrototype(p, Person) {
let target = p.__proto__;
while(target) {
if (target === Person.prototype) {
break;
}
target = target.__proto__;
}
return !!target;
}
testIsPrototype(person, Person) // true
testIsPrototype(person, Child) // true
// 或者
function instanceOf(left, right) {
let leftValue = left.__proto__;
let rightValue = right.prototype;
while (true) {
if (leftValue === null) {
return false;
}
if (leftValue === rightValue) {
return true;
}
leftValue = leftValue.__proto__;
}
}
十、一道原型链的例子
function Foo() {
Foo.a = function () {
console.log('1')
}
this.a = function () {
console.log('2')
}
}
Foo.prototype.a = function () {
console.log('3')
}
Foo.a = function () {
console.log('4')
}
Foo.a();
const obj = new Foo();
obj.a();
Foo.a();
输出 4、2、1
这道题可以先去看看这篇内容 一道经典的JavaScript面试题
参考链接:
重新认识javascript对象(三)——原型及原型链
最详尽的 JS 原型与原型链终极详解(一)
一篇文章带你理解原型和原型链
关于原型和原型链的精辟解读
javascript——原型与原型链