前言
大家好,我是林三心,相信大家都聽過前端的三座大山:閉包,原型鏈,作用域,這三個其實都只是算基礎。而我一直覺得基礎是進階的前提,所以不能因為是基礎就忽視他們。今天我就以我的方式講講原型鏈吧,希望大家能牢固地掌握原型鏈知識
很多文章一上來就扔這個圖,但是我不喜歡這樣,我覺得這樣對基礎不好的同學很不好,我喜歡帶領大家去從零實現這個圖,在實現的過程中,不斷地掌握原型鏈的所有知識!!!來吧!!!跟著我從零實現吧!!!跟著我馴服原型鏈吧!!!
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
函式
我們們上面提到了建構函式
,其實他說到底也是個函式,其實我們們平時定義函式,無非有以下幾種
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歲
我們之前說過,建構函式
的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
物件
我們們平常開發中,建立一個物件,通常會用以下幾種方法。
建構函式建立物件
,他建立出來的物件都是此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 }
我們之前說過,建構函式
的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
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
constructor
constructor和prototype是成對的,你指向我,我指向你。舉個例子,如果你是我老婆,那我肯定是你的老公。
function fn() {}
console.log(fn.prototype) // {constructor: fn}
console.log(fn.prototype.constructor === fn) // true
原型鏈
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
什麼是原型鏈?
什麼是原型鏈呢?其實俗話說就是:__proto__的路徑
就叫原型鏈
原型鏈終點
上面我們們看到,三條原型鏈結尾都是Object.prototype
,那是不是說明了Object.prototype
就是原型鏈的終點呢?其實不是的,Object.prototype
其實也有__proto__,指向null,那才是原型鏈的終點
至此,整個原型示意圖就畫完啦!!!
原型繼承
說到原型,就不得不說補充一下原型繼承
這個知識點了,原型繼承
就是,例項
可以使用建構函式上的prototype
中的方法
function Person(name) { // 建構函式
this.name = name
}
Person.prototype.sayName = function() { // 往原型物件新增方法
console.log(this.name)
}
const person = new Person('林三心') // 例項
// 使用建構函式的prototype中的方法
person.sayName() // 林三心
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
結語
如果你覺得此文對你有一丁點幫助,點個贊,鼓勵一下林三心哈哈。或者加入我的群哈哈,我們們一起摸魚一起學習