深入理解js原型和原型链

299 阅读6分钟

a77c816982d6c8efdf0c9cbf2b0bd4c7.jpeg

思维导图

js原型和原型链.png

1. js的发展历史

1.1 javaScript的诞生

JavaScript因为互联网而生,紧随着浏览器的出现而问世。

1994年,网景公司(Netscape)发布了Navigator浏览器0.9版。这是历史上第一个比较成熟的网络浏览器,轰动一时。但是,这个版本的浏览器只能用来浏览,不具备与访问者互动的能力。比如,如果网页上有一栏"用户名"要求填写,浏览器就无法判断访问者是否真的填写了,只有让服务器端判断。如果没有填写,服务器端就返回错误,要求用户重新填写,这太浪费时间和服务器资源了。因此,网景公司急需一种网页脚本语言,使得浏览器可以与网页互动。

bg2011062401.jpeg

网页脚本语言到底是什么语言?网景公司当时有两个选择:一个是采用现有的语言,比如Perl、Python、Tcl、Scheme等等,允许它们直接嵌入网页;另一个是发明一种全新的语言。这两个选择各有利弊。第一个选择,有利于充分利用现有代码和程序员资源,推广起来比较容易;第二个选择,有利于开发出完全适用的语言,实现起来比较容易。

1995年收到Java面向对象语言的影响,网景公司和Sun联盟,开发一种可以运行在网页中的语言,并命名为 "Java+script"

1.2 js的设计思想

1995年5月,Brendan Eich只用了10天,就设计完成了这种语言的第一版。它是一个大杂烩,语法有多个来源:

  • 基本语法:借鉴C语言和Java语言。
  • 数据结构:借鉴Java语言,包括将值分成原始值和对象两大类。
  • 函数的用法:借鉴Scheme语言和Awk语言,将函数当作第一等公民,并引入闭包。
  • 原型继承模型:借鉴Self语言(Smalltalk的一种变种)。
  • 正则表达式:借鉴Perl语言。
  • 字符串和数组处理:借鉴Python语言。

2. js继承的思想设计

JavaScript中的数据类型都是对象,必须有一种机制,将所有的对象联系起来,于是设计了 "继承"。es6之前是没有类的概念的(设计者认为如果引入class类的概念, js就是一种完整的面向对象编程语言,过于正式,而且增加了入门难度)

Java 中生成实例

Person p = new Person()

因此JS 引入了new命令,用来生成实例对象,但是js没有类的概念,类比c++和Java 都会调用类的构造函数(constructor),简化之后在js中 new命令后面跟的不是类 而是 构造函数

JavaScript生成实例

function Person(name){
    this.name = name
    this.type="人" 
} 
var p1 = new Person("张三") // 实例 
var p2 = new Person("李四") // 实例

new 运算符的缺点

无法共享属性和方法;每一个实例对象,都有自己的属性和方法的副本。这不仅无法做到数据共享,也是极大的资源浪费。

例如:p1修改了type属性之后,p2不会被影响

p1.type ="动物" console.log(p2)//{name:"李四",type:"人"}

3. 为什么要设计原型对象prototype

为了解决共享问题,Brendan Eich 为构造函数设置了一个prototype属性。这个属性指向一个对象(原型对象),所有实例共享的属性和方法,都存放在这个对象里面。不需要共享的属性和方法(私有属性、方法)则放在构造函数中。实例对象一旦创建,将自动引用prototype对象的属性和方法。也就是说,实例对象的属性和方法,分成两种,一种是本地的,另一种是引用的。

Animal.prototype.type="动物"
Animal.prototype.say = function(content){
  console.log(`${this.name}${content}`)
}
var dog = new Animal('dog')
dog.say('汪汪汪')  //  dog,汪汪汪
var cat = new Animal('cat')
cat.say('喵喵喵') // cat,喵喵喵

对象关系.png

4.什么是原型对象

概念:每个JavaScript对象(除了null)创建的时候,都会与之关联一个对象,这个对象就是原型对象,每个实例对象都会从原型对象中继承属性。

构造函数和实例原型的关系

对象属性和实例原型.png 每个实例对象都有一个隐式原型__proto__属性,指向他的实例原型

function Animal() {

}
var animal = new Animal();
console.log(animal .__proto__ === Animal.prototype); // true

__proto__和原型对象的关系.png

5. 实例对象、原型对象、构造函数的关系

constructor:每个原型都有一个constructor属性,指向其关联的构造函数

function Animal() {

}
console.log(Animal.prototype.constructor === Animal) // true

构造函数属性.png

你是否真的理解了?看看这张图

关系.png 补充:在js中,所有的function函数都是由Function继承来的,可以说是Function是所有 function的祖宗。而Function则是由自己衍生来的,如图中它的__proto__指向了自己的原型对象

总结

  • 所有实例对象的__proto__属性,都指向其构造函数的原型对象
  • 所有的函数(构造函数),都是new Function()的实例,所以函数的__proto__ 指向Function构造函数的原型对象Function.prototype
  • 所有的的原型对象,都是new Object()的实例,所以原型对象的__proto__指向Objec构造函数的原型对象Object.prototype, 而Object.prototype 指向null
  • Function构造函数本身就是Function的实例,所以_proto_指向Function的原型对象。

6.什么是原型链

顾名思义原型链是一条链,既然每个对象都有一个_proto_属性指向原型对象,那么原型对象也有_proto_指向原型对象的原型对象,直到指向中的null,这才到达原型链的顶端。

7.拓展知识点

获取对象的原型对象

function Animal() {

}
var animal = new Animal()
console.log(animal) 

console.log(Object.getPrototypeOf(animal)) // 推荐
console.log(animal.__proto__) // 不推荐
console.log(animal.constructor.prototype) // 不推荐 利用每个实例对象都从原型中继承了一个constructor属性

获取对象属性和原型属性

  • Object.keys(): 返回该对象上所有可枚举的属性,不包括原型链
  • getOwnProperyNames():返回对象自身的属性(可枚举、不可枚举),不包含原型链上的属性
  • obj.hasOwnProperty('xx'): 判断对象是否包含属性(不包含原型链上的属性)
  • for ...in 返回所有可枚举的属性 包含原型链

8.练习巩固

题目一

function Person() {     
}
var p = new Person()
console.log(Person.prototype); // Object {}
console.log(p.prototype); // undefined 实例没有prototype属性
console.log(p.constructor); // function Person(){} 实例继承了构造函数的原型对象,从而继承了constructor属性,指向构造函数本身
console.log(Person.constructor); // function Function{} 原理同上 每个构造函数都是通过 new Function() 构造而来
console.log({}.constructor); // funciton Object() {}     每个对象都是通过new Object()构造的
console.log(Object.constructor); // function Function(){}
console.log([].constructor);// function Array(){}

题目二


function Person(){

}
var person1=new Person();

person1.__proto__==   // Person.prototype
person1.constructor== //Person
Person.__proto__== //Function.prototype
Person.prototype.constructor== //Person
person1.prototype.constructor== // 报错  person1.prototype== undefined
Person.prototype== // person1.__proto__

题目三


function Fun(){
  var getName = function(){
      console.log(1);
  }
  return this;
}
Fun.getName = function(){
  console.log(2);
}
Fun.prototype.getName = function(){
  console.log(3);
}
var getName = function(){
  console.log(4);
}
function getName(){
  console.log(5);
}
      
Fun().getName(); //4 Fun函数return this ,this指向Window, 函数表达式提升,之后会被函数声明重新赋值
getName(); //4 知识点:变量提升
new Fun().getName();//3 等价于 var f = new Fun(); f.getName() // 调用原型对象上的属性 方法
new new Fun().getName();//3

参考