動態

詳情 返回 返回

這可能是思否講「原型鏈」,講的最好最通俗易懂的了,附練習題! - 動態 詳情

前言

大家好,我是林三心,相信大家都聽過前端的三座大山:閉包,原型鏈,作用域,這三個其實都只是算基礎。而我一直覺得基礎是進階的前提,所以不能因為是基礎就忽視他們。今天我就以我的方式講講原型鏈吧,希望大家能牢固地掌握原型鏈知識

很多文章一上來就扔這個圖,但是我不喜歡這樣,我覺得這樣對基礎不好的同學很不好,我喜歡帶領大家去從零實現這個圖,在實現的過程中,不斷地掌握原型鏈的所有知識!!!來吧!!!跟着我從零實現吧!!!跟着我馴服原型鏈吧!!!

截屏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

結語

如果你覺得此文對你有一丁點幫助,點個贊,鼓勵一下林三心哈哈。或者加入我的羣哈哈,咱們一起摸魚一起學習
image.png

Add a new 評論

Some HTML is okay.