javascript进阶知识17- 原型与原型链

66 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第8天,点击查看活动详情

1、原型

prototype

在js中每一个函数都有一个属性,prototype,这个属性是一个对象,在这个对象上,默认会有一个属性constructor,constructor是一个函数,这个函数指回与之关联的构造函数。

image.png

proto

在自定义一个函数时,原型对象只会默认获得constructor属性,其他的方法都继承自Object。而每次使用构造函数创造一个新的对象时,这个对象就会默认获得一个属性__proto__,而这个__proto__指向的就是构造函数的prototype。

function Person(name) {
    this.name = name;
}
let ly = new Person('ly')
console.log(ly.__proto__)
console.log(ly.__proto__ === Person.prototype) //true

image.png

例子

比如我们日常使用字面量定义一个对象,其实底层调用的还是使用的new Object()来构造的对象。所以我们可以发现我们定义的对象都有一个属性__proto__,而且它和Object构造函数的prototype也是相等的。

image.png

又比如,我们使用的布尔值、字符串、Number等在调用其方法时,底层都默认将其转换成了调用他们的构造函数生成的对象。

image.png

所以我们才可以使用原始值上面的方法(方法只有对象才有)。

查看原型的方法

Object.getPrototypeOf()

这个方法其实和使用__proto__查看差不多,都会返回该对象的原型对象。

function Person(name) {
    this.name = name
}
let ly = new Person('ly')
console.log(Object.getPrototypeOf(ly) === Person.prototype)  // true

设置原型的方法

Object.setPrototypeOf()

let bid = {
    num:2
}
let person = {
    name:'ly'
}

Object.setPrototypeOf(person,bid)

console.log(person.name)  // ly
console.log(person.num)   // 2
console.log(Object.getPrototypeOf(person) === bid)   // true

原型的作用

在使用构造函数时,如果我们在构造函数上面创建了一个公共的方法,那么每一次new这个构造函数生成对象的时候,都会给生成的对象上面开创一个内存用于存放这个公共的方法。如果我们使用了这个构造函数创建了很多对象,那么就会浪费空间,所以我们可以把公共的方法或者属性放在构造函数的原型(prototype)上面。

function User(name) {
    this.name = name;
    this.show = function() {
        console.log(this.name);
    }
}

let p1 = new User('ly');
let p2 = new User('jack');
p1.show(); // ly
p2.show(); //jack

console.log(p1);
console.log(p2);

image.png

function User(name) {
    this.name = name;
}

User.prototype.show = function() {
    console.log(this.name)
}

let p1 = new User('ly');
let p2 = new User('jack');
p1.show(); // ly
p2.show(); //jack

console.log(p1);
console.log(p2);

image.png

2、原型链

有时候,在使用构造函数创建一个新的对象的时候,我们发现我们生成的对象上面除了有构造函数原型上面的属性以外,还有其他的属性可以供我们使用,但是当打印生成的对象时,我们却又发现,该对象上面的属性又没有这些方法,那么这些方法是从哪里的来的呢?

——————其实这些方法都是这个函数的原型对象从Object的原型对象上继承来的。这就是原型链。

function Person(name) {
    this.name = name
}
let user = new Person('ly')
user.hasOwnProperty('name')  //true

image.png

注:[[Prototype]]就是__proto__

image.png

因为prototype是对象嘛,那么该对象是Object构造函数创建的,而且对象就一定有__proto__属性,所以prototype__proto__属性指向的就是Object构造函数的prototype

我们可以打印看一下他们是否相等:

function Person(name) {
    this.name = name
}
let user = new Person('ly')
console.log(user.__proto__.__proto__ === Object.prototype) // true

image.png

那么我们又可以猜想一下,Object是构造函数,但也是对象,那么这个Object是不是也就应该同时有__proto__prototype属性呢?答案是对的。

image.png

Object__proto__指向的应该就是生成该对象的构造函数的原型对象,而构造Object对象的构造函数又是谁呢?他就是Function构造函数。我们所有的函数都是使用new Function()创造的(底层上)。所以Object的__proto__就应该等于Function的prototype

image.png

Function身上的prototype又是对象,我们之前也说了对象都是使用的Object构造函数生成的,所以Function.prototype.__proto__就应该等于Object.prototype了:

image.png

再回到之前的,Object也是对象,该对象除了__proto__也有prototype属性,这个属性的是对象,那么他的__proto__又指向谁呢? -----答案是它指向的null,也就是原型链的顶层。

image.png

同时,Function也是既是函数也是对象,那么它是不是也是由Function创建的呢?答案是对的,Function.__proto__指向的就是Function.prototype:

image.png

是不是晕了,哈哈哈!

总结

1、在js中,原型链的顶层是null
2、在js中,万物皆是对象,任何对象都是由构造函数创建
3、在js中,所有的函数都是由Function构造函数创建
4、在js中,所有不是由构造函数创建的对象,默认都是由Object构造函数创建
5、Object和Function都又是构造函数、又是对象。所以Object.__proto__指向Function.prototype, Function.prototype.proto 指向 Object.prototype
6、Function的__proto__指向的是自己的prototype
7、Object.prototype.null指向的是原型链顶层,null

其实我感觉只要把Function和Object这里的原型链看懂了,js的原型链就应该懂了,因为难的地方其实就在这里。

原型链的顺序

在我们使用一个对象的方法或者属性时,如果这个对象本身没有这个属性或者方法,那么,js就会顺着这个原型链往上找,如果找到了,就返回这个属性或者方法,如果顺着原型链到了原型链的顶层,就会返回null。

原型链的改变

其实原型链是可以被改变的,那就是使用继承,将本来一个对象的__proto__指向Object.prototype改为指向其他的对象了,但是改变后,原理还是不变的。这个就下次再说吧。