JavaScript核心原理三部曲:《原型继承:面向对象编程的终极指南》

138 阅读5分钟

前言

在之前JavaScript中暂时的错误的介绍new的底层原理,本篇文章将带来new的正确的底层原理

万物皆对象

原型对象(显示原型)

  • 函数定义出来天生拥有一个属性protype(原型),是一个对象
  • 意义 : 1.让函数构造中的一些固定的属性和方法挂在到原型上,在创建实例的时候,就不需要重复的执行这些属性和方法
  • 挂载在原型上的属性和方法,在实例对象上可以直接访问到
  • 实例对象是无法修改原型上的属和方法的
  • 实例对象也无法删除原型上的属性和方法
Person.prototype.age = 11;
function Person() {
  this.name = "pp";
  // this.age = 18;
}
const p1 = new Person();
console.log(p1.age + " " + p1.name);//实例对象p1可以直接访问挂载在原型上的属性和方法 p1.name

结果图:

image.png

Person.prototype.say = function () {
  console.log("我是pp");
};
Person.prototype.age = 11;
function Person() {
  this.name = "pp";
}
const p1 = new Person();
 p1.say();
p1.say = "hello";//添加了给构造函数添加了一个显式属性,并没有修改我们原型对象上的属性
console.log(p1.say);
console.log(p1.say());



结果图: image.png

注意:这里需要注意的是,这里看似是成功修改了p1的say()方法,将方法修改为属性,但是这并不是修改而是添加了一个新的属性say在构造函数中,因为我们是新创建的一个对象,这个步骤相当于添加一个属性在p1上,覆盖了我们原型默认的,那我们如果就是想要原来默认的say()方法,可以使用Person.prototype.say() 结果图:

image.png

对象原型(隐式原型)

1.v8引擎中,每一个对象都拥有一个属性__proto__,这个属性值也是一个对象

2.v8在访问对象中的属性时,会先访问对象上的显示拥有的属性(构造函数有的属性,或者我们在对象上添加的属性),如果找不到,就回去对象上的隐式原型(对象原型)中查找,也就是__proto__中查找

3.实例对象的隐式原型===构造函数的显示原型

4.实例对象的隐式原型===构造函数的显示原型 这是为什么呢? new原理导致的

5.这么设计意义是什么?

让实例对象继承到构造函数原型上的方法和属性,方便我们为某一个数据类型添加属性和方法 如下: 为什么引用数据类型的数组身上有push方法?

const arr=[]

arr.push(1)

这个过程相当于

 const arr=new Array()//默认指行,arr其实是对象,万物皆对象
arr.__proto__===Array.prototype

[JavaScript : 万物皆对象?带你理解对象创建过程和数据类型的判断 引言:万物皆对象 在JavaScript中 - 掘金](url)

所以arr的push()是来自Array()的原型对象上的

new 的原理

1.创建一个空对象

2.让构造函数的this执行这个空对象

3.执行构造函数中的代码

3.将空对象的隐式原型,__proto__赋值成构造函数的显示原型

5.返回这个对象


  var obj={}//1

  Person.call(obj)//2让this===obj这的call()方法会在下一篇文章讲到

  this.name='pp'3

  obj.__proto__=Person.prototype//4

  return obj//5

constructor

实例对象的对象原型(隐式原型)和构造函数的原型(原型对象/显示原型)上都有constructor属性,但对象原型__proto__的constructor是继承自原型对象prototype的,对象原型__proto__是被原型对象prototype赋值的,所以它们的constructor是相同的,constructor都是指向创建对象原型和原型对象的构造函数的。

  • 意义:是为了让所有实例对象都知道自己是从哪个构造函数创建的

image.png Person+()是调用函数,在函数 Person 内部,如果没有显式返回一个对象,那么构造函数会默认返回新创建的对象(即实例)。但是,如果我们把 Person 当作普通函数调用(而不是通过 new),如果 Person 函数没有返回值(即没有 return 语句),则返回 undefined,如果 Person 函数有返回值,则返回该值。

第二个Person这是引用,从上到下查找变量或函数名,这只有Person函数,Person代表的就是Person函数体 那么构造函数与对象原型、原型对象之间的关系如下图:

image.png

原型链

那么函数身上的原型对象是对象,那原型对象也有对象原型(隐式原型),函数也是对象,函数也是对象,函数也有对象原型(隐式原型)

v8在访问对象的属性时,会先访问对象上的显示拥有的属性,如果找不到,就回去对象上的隐式原型中查找,也就是__proto__中查找,如果还找不到,就会去对象原型__proto__的对象原型__proto__中查找,层层往上,直到找到或者找不到为止null

v8的这种链状关系就叫原型链

  function grandParent(){
    //grandParent.prototype=new Object();
  // grandParent.prototype.__prototype=Object.prototype;
 // Object.prototype.__prototype__ == null
    this.name = "grandParent";
    this.card  = "idcard";
  }
  Parent.prototype = new grandParent();//{name:"grandParent",card:"idcard"}

  function Parent() {
    this.LastName = "张";
  }
 
  Child.prototype = new Parent();//{LastName:"张"}.__proto__ = {name:"grandParent",card:"idcard"}
 function Child() {
  this.name = "张三";
this.age = 18;
}
const child = new Child();
console.log(child.LastName);
console.log(child.card);
</script>


不是每个对象都有原型,没有原型的对象

  • 创建对象的一种方式 const obj=Object.create() image.png
  • Object.create(obj)创建一个新对象,让这个新对象的隐式原型等于传入的obj
  • Objecr.create(null)得到一个没有原型的对象

image.png

image.png

1.Foo构造函数 f2,f2实例化对象. __proto__ ===Foo.prototype

f1,f2的对象原型被Foo构造函数的原型(原型对象/显示原型)赋值

Foo.prototype.constructor指向Foo构造函数

Foo构造函数也是对象,Foo.__proto__===Function.prototype

所有函数都由Fuction创建,所有函数.__proto___===Function.prototype

2.Function构造函数

Fuction构造函数实例对象的对象原型===Fuction.prototype

Fuction.prototype.__proto__===Object.prototype

3.Object构造函数

Object构造函数实例对象o1,o2的对象原型===Object.prototype

Object.prototype的对象原型__proto__最终指向空

Object.prototype.constructor指向Object构造函数

Object构造函数是函数,所以其对象原型===Fuction.prototype

所有原型对象.__proto__===Object.prototype