注册

这可能是掘金讲「原型链」,讲的最好最通俗易懂的了,附练习题!


前言


大家好,我是林三心,相信大家都听过前端的三座大山:闭包,原型链,作用域,这三个其实都只是算基础。而我一直觉得基础是进阶的前提,所以不能因为是基础就忽视他们。今天我就以我的方式讲讲原型链吧,希望大家能牢固地掌握原型链知识


很多文章一上来就扔这个图,但是我不喜欢这样,我觉得这样对基础不好的同学很不好,我喜欢带领大家去从零实现这个图,在实现的过程中,不断地掌握原型链的所有知识!!!来吧!!!跟着我从零实现吧!!!跟着我驯服原型链吧!!!


截屏2021-09-13 下午9.58.41.png


prototype和__proto__


是啥


这两个东西到底是啥呢?


  • prototype: 显式原型
  • __ proto__: 隐式原型

有什么关系


那么这两个都叫原型,那他们两到底啥关系呢?


一般,构造函数的prototype和其实例的__proto__是指向同一个地方的,这个地方就叫做原型对象


那什么是构造函数呢?俗话说就是,可以用来new的函数就叫构造函数,箭头函数不能用来当做构造函数哦


function Person(name, age) { // 这个就是构造函数
this.name = name
this.age = age
}

const person1 = new Person('小明', 20) // 这个是Person构造函数的实例
const person2 = new Person('小红', 30) // 这个也是Person构造函数的实例

构造函数的prototype和其实例的__proto__是指向同一个地方的,咱们可以来验证一下


function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.sayName = function() {
console.log(this.name)
}
console.log(Person.prototype) // { sayName: [Function] }

const person1 = new Person('小明', 20)
console.log(person1.__proto__) // { sayName: [Function] }

const person2 = new Person('小红', 30)
console.log(person2.__proto__) // { sayName: [Function] }

console.log(Person.prototype === person1.__proto__) // true
console.log(Person.prototype === person2.__proto__) // true

截屏2021-09-12 下午9.23.35.png


函数


咱们上面提到了构造函数,其实他说到底也是个函数,其实咱们平时定义函数,无非有以下几种


function fn1(name, age) {
console.log(`我是${name}, 我今年${age}岁`)
}
fn1('林三心', 10) // 我是林三心, 我今年10岁

const fn2 = function(name, age){
console.log(`我是${name}, 我今年${age}岁`)
}
fn2('林三心', 10) // 我是林三心, 我今年10岁

const arrowFn = (name, age) => {
console.log(`我是${name}, 我今年${age}岁`)
}
arrowFn('林三心', 10) // 我是林三心, 我今年10岁

其实这几种的本质都是一样的(只考虑函数的声明),都可以使用new Function来声明,是的没错Function也是一个构造函数。上面的写法等同于下面的写法


const fn1 = new Function('name', 'age', 'console.log(`我是${name}, 我今年${age}岁`)')
fn1('林三心', 10) // 我是林三心, 我今年10岁

const fn2 = new Function('name', 'age', 'console.log(`我是${name}, 我今年${age}岁`)')
fn2('林三心', 10) // 我是林三心, 我今年10岁

const arrowFn = new Function('name', 'age', 'console.log(`我是${name}, 我今年${age}岁`)')
arrowFn('林三心', 10) // 我是林三心, 我今年10岁

截屏2021-09-12 下午9.17.42.png


我们之前说过,构造函数prototype和其实例__proto__是指向同一个地方的,这里的fn1,fn2,arrowFn其实也都是Function构造函数的实例,那我们来验证一下吧


function fn1(name, age) {
console.log(`我是${name}, 我今年${age}岁`)
}

const fn2 = function(name, age){
console.log(`我是${name}, 我今年${age}岁`)
}

const arrowFn = (name, age) => {
console.log(`我是${name}, 我今年${age}岁`)
}

console.log(Function.prototype === fn1.__proto__) // true
console.log(Function.prototype === fn2.__proto__) // true
console.log(Function.prototype === arrowFn.__proto__) // true

截屏2021-09-12 下午9.29.00.png


对象


咱们平常开发中,创建一个对象,通常会用以下几种方法。


  • 构造函数创建对象,他创建出来的对象都是此Function构造函数的实例,所以这里不讨论它
  • 字面量创建对象
  • new Object创建对象
  • Object.create创建对象,创建出来的是一个空原型的对象,这里不讨论它

// 第一种:构造函数创建对象
function Person(name, age) {
this.name = name
this.age = age
}
const person1 = new Person('林三心', 10)
console.log(person1) // Person { name: '林三心', age: 10 }

// 第二种:字面量创建对象
const person2 = {name: '林三心', age: 10}
console.log(person2) // { name: '林三心', age: 10 }

// 第三种:new Object创建对象
const person3 = new Object()
person3.name = '林三心'
person3.age = 10
console.log(person3) // { name: '林三心', age: 10 }

// 第四种:Object.create创建对象
const person4 = Object.create({})
person4.name = '林三心'
person4.age = 10
console.log(person4) // { name: '林三心', age: 10 }

咱们来看看字面量创建对象new Object创建对象两种方式,其实字面量创建对象的本质就是new Object创建对象


// 字面量创建对象
const person2 = {name: '林三心', age: 10}
console.log(person2) // { name: '林三心', age: 10 }

本质是

// new Object创建对象
const person2 = new Object()
person2.name = '林三心'
person2.age = 10
console.log(person2) // { name: '林三心', age: 10 }

截屏2021-09-12 下午9.52.47.png


我们之前说过,构造函数prototype和其实例__proto__是指向同一个地方的,这里的person2,person3其实也都是Object构造函数的实例,那我们来验证一下吧


const person2 = {name: '林三心', age: 10}

const person3 = new Object()
person3.name = '林三心'
person3.age = 10

console.log(Object.prototype === person2.__proto__) // true
console.log(Object.prototype === person3.__proto__) // true

截屏2021-09-12 下午9.58.31.png


Function和Object


上面咱们常说


  • 函数Function构造函数的实例
  • 对象Object构造函数的实例

Function构造函数Object构造函数他们两个又是谁的实例呢?


  • function Object()其实也是个函数,所以他是Function构造函数的实例
  • function Function()其实也是个函数,所以他也是Function构造函数的实例,没错,他是他自己本身的实例

咱们可以试验一下就知道了


console.log(Function.prototype === Object.__proto__) // true
console.log(Function.prototype === Function.__proto__) // true

截屏2021-09-12 下午10.12.40.png


constructor


constructor和prototype是成对的,你指向我,我指向你。举个例子,如果你是我老婆,那我肯定是你的老公。


function fn() {}

console.log(fn.prototype) // {constructor: fn}
console.log(fn.prototype.constructor === fn) // true

截屏2021-09-12 下午10.35.40.png


原型链


Person.prototype 和 Function.prototype


讨论原型链之前,咱们先来聊聊这两个东西


  • Person.prototype,它是构造函数Person的原型对象
  • Function.prototype,他是构造函数Function的原型对象

都说了原型对象,原型对象,可以知道其实这两个本质都是对象


那既然是对象,本质肯定都是通过new Object()来创建的。既然是通过new Object()创建的,那就说明Person.prototype 和 Function.prototype都是构造函数Object的实例。也就说明了Person.prototype 和 Function.prototype他们两的__proto__都指向Object.prototype


咱们可以验证一下


function Person(){}

console.log(Person.prototype.__proto__ === Object.prototype) // true
console.log(Function.prototype.__proto__ === Object.prototype) // true

截屏2021-09-12 下午10.46.41.png


什么是原型链?


什么是原型链呢?其实俗话说就是:__proto__的路径就叫原型链


截屏2021-09-12 下午10.55.48.png


原型链终点


上面咱们看到,三条原型链结尾都是Object.prototype,那是不是说明了Object.prototype就是原型链的终点呢?其实不是的,Object.prototype其实也有__proto__,指向null,那才是原型链的终点


至此,整个原型示意图就画完啦!!!


截屏2021-09-13 下午9.56.10.png


原型继承


说到原型,就不得不说补充一下原型继承这个知识点了,原型继承就是,实例可以使用构造函数上的prototype中的方法


function Person(name) { // 构造函数
this.name = name
}
Person.prototype.sayName = function() { // 往原型对象添加方法
console.log(this.name)
}


const person = new Person('林三心') // 实例
// 使用构造函数的prototype中的方法
person.sayName() // 林三心

截屏2021-09-12 下午11.10.41.png


instanceof


使用方法


A instanceof B

作用:判断B的prototype是否在A的原型链上


例子


function Person(name) { // 构造函数
this.name = name
}

const person = new Person('林三心') // 实例

console.log(Person instanceof Function) // true
console.log(Person instanceof Object) // true
console.log(person instanceof Person) // true
console.log(person instanceof Object) // true

练习题


练习题只为了大家能巩固本文章的知识


第一题


var F = function() {};

Object.prototype.a = function() {
console.log('a');
};

Function.prototype.b = function() {
console.log('b');
}

var f = new F();

f.a();
f.b();

F.a();
F.b();

答案


f.a(); // a
f.b(); // f.b is not a function

F.a(); // a
F.b(); // b

第二题


var A = function() {};
A.prototype.n = 1;
var b = new A();
A.prototype = {
n: 2,
m: 3
}
var c = new A();

console.log(b.n);
console.log(b.m);

console.log(c.n);
console.log(c.m);

答案


console.log(b.n); // 1
console.log(b.m); // undefined

console.log(c.n); // 2
console.log(c.m); // 3

第三题


var foo = {},
F = function(){};
Object.prototype.a = 'value a';
Function.prototype.b = 'value b';

console.log(foo.a);
console.log(foo.b);

console.log(F.a);
console.log(F.b);

答案


console.log(foo.a); // value a
console.log(foo.b); // undefined

console.log(F.a); // value a
console.log(F.b); // value b

第四题


function A() {}
function B(a) {
this.a = a;
}
function C(a) {
if (a) {
this.a = a;
}
}
A.prototype.a = 1;
B.prototype.a = 1;
C.prototype.a = 1;

console.log(new A().a);
console.log(new B().a);
console.log(new C(2).a);

答案


console.log(new A().a); // 1
console.log(new B().a); // undefined
console.log(new C(2).a); // 2

第五题


console.log(123['toString'].length + 123)

答案:123是数字,数字本质是new Number(),数字本身没有toString方法,则沿着__proto__function Number()prototype上找,找到toString方法,toString方法的length是1,1 + 123 = 124,至于为什么length是1,可以看95%的人都回答不上来的问题:函数的length是多少?


console.log(123['toString'].length + 123) // 124

结语



如果你觉得此文对你有一丁点帮助,点个赞,鼓励一下

0 个评论

要回复文章请先登录注册