JS -原型与原形链

其实网上讲原型和原形链关系的文章有很多,现在只是做个笔记摘要,一开始看确实挺懵的,还是要多看几遍才行。

一、原型与原型链

原型:每一个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_ 属性,它指向构造函数的原型对象

可以得出一些结论:

  1. 构造函数Person自带prototype属性,它是一个对象,可以在上面定义属性和方法
  2. 构造函数的原型 Person.prototype自带constructor属性指向它的构造函数(Person)
  3. 函数也是一个对象,所以他也带有_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
七、一些题目
  1. person1.proto 是什么?
  2. Person.proto 是什么?
  3. Person.prototype.proto 是什么?
  4. Object.proto 是什么?
  5. 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有以下几种方法检测一个对象的原型:

  1. isPrototypeOf():检测一个对象是否是另一个对象的原型
  2. Object.getPrototypeOf(): 获得对象的直接原型。
  3. instanceof: 检测 constructor.prototype 是否存在于参数 object 的原型链上
    object instanceof constructor
  4. obj.constructor.prototype:检测非Object.create()创建的对象的原型

其他

  1. 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——原型与原型链

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值