原型与原型链

MDN解释:

JavaScript 常被描述为一种基于原型的语言 (prototype-based language)——每个对象拥有一个原型对象,对象以其原型为模板、从原型继承方法和属性。原型对象也可能拥有原型,并从中继承方法和属性,一层一层、以此类推。这种关系常被称为原型链 (prototype chain),它解释了为何一个对象会拥有定义在其他对象中的属性和方法。

方方解释:

所有对象都有 toString 和 valueOf 属性,那么我们是否有必要给每个对象一个 toString 和 valueOf 呢?
明显不需要。
JS 的做法是把 toString 和 valueOf 放在一个对象里(暂且叫做公用属性组成的对象)
然后让每一个对象的 __ proto __ 存储这个「公用属性组成的对象」的地址。
原型就是共有属性

公用属性(原型)

用String赋值的变量s1,她的__ proto __ 指向了String共有属性(原型),String的原型里面还有 __ proto __的原型指向Object共有属性,Object共有属性最后指向了null,结构图如下:


  • Numer的共有属性:Number.prototype
  • Object的共有属性:Object.prototype
  • String的共有属性:String.prototype
  • Boolean的共有属性:Boolean.prototype

上面的代码Object.prototype就是上边结构图的Object的共有属性(原型),
o1的__ proto __指向Object里的原型,所以是true

n1的下划线原型指向的是Number的原型,然后Number原型里面的下划线原型指向Object的原型,所以就可以写成

n1.__proto__.__proto__ === Object.prototype
或者:Number.prototype.__proto__ === Object.prototype


注意:prototype是浏览器自动生成的,你就算不写代码也有

上面的图当你什么代码也不写的时候,打开浏览器也会有一个window对象,而window对象里有Number、Object、String、Boolean这几个全局函数,又因为函数也是对象,所以他们再次将内容存放到一个堆里,又因为是函数所以有prototype的原型,分别指向自己对应的类型,而最后prototype也是一个对象,只要是对象就有__ proto __这个下划线的原型,通过他指向了Object公用属性。就拿Number这个全局函数来举例

上面没有执行任何操作,直接打印了Number这个函数,它里面的prototype就指向了Number的公用属性,而她的prototype下面还有一个下划线的原型指向了Object的公用属性。

上面的代码,var 后面的肯定是对象,new后面的是函数对象

对象的下划线原型指向该实例对象的构造函数的原型也就是说:

var n = new Object();
n.__proto__ === Object.prototype
//new后面的就是构造函数名,以new方法生成的函数就是构造函数

Object.prototype.proto ===null

对象由构造函数创建的

数组、函数、正则都是对象

对象的proto等于创建者的prototype

prototype上有个属性constructor 指向创建者(构造函数)

数组的构造函数是 Array

函数的构造函数是 Function(当构造函数作为对象时,这个构造函数的构造函数就是Function)

正则的构造函数是 RegExp

而String,Number,Boolean,Object,Function这几个是全局函数,所以它的构造函数就是Function,故指向Function.prototype:


这里最需要注意的是Function的构造函数就是Function,只有它的proto是指向自己的:


另外自己写的构造函数也是一样的,当构造函数作为对象时.__ proto __===Function.prototype

如:

function Person(name){
  this.name = name;
}
var person = new Person('wang')
Person.__proto__===Function.prototype  //true
//因为Person是一个函数,函数也是一个对象所以有__proto__的属性,而函数的构造函数是Function

另外判断一个对象的构造函数可以用
1.console.log(xxx.constructor),但是注意xxx只能是单独的对象就是xxx里面不可以接prototype或者__ proto __


第一个Function.constructor是可以正确判断的,但Function.prototype.constructor就不能,它显示的也是Function可实际上它的构造函数是Object。另外判断一个构造函数是什么其实上述方法并不可取,因为不排除有人为改变构造函数的可能,所以暂且只需记住就行,另外只要是(对象.prototype)或者(对象.__ proto __)作为对象的时候他们的构造函数都是Object(这里排除特例)
2.实例对象.constructor(构造器里的prototype始终指向构造函数本身)

实例对象的proto和构造函数中的prototype相等--->true
因为实例对象是通过构造函数来创建的,构造函数中有原型对象prototype
实例对象的proto指向了构造函数的原型对象prototype
只要是对象就有proto原型
又因为prototype也是对象,所以,prototype也有proto

原型(对象)、构造函数、实例、原型链

关系图如下:


1.构造函数:就是函数,名字首字母应该大写
function Foo(name,age){
  this.name = name
  this.age = age
  this.class = 'class-1'
  // return this // 默认有这行
}
2.实例对象:new 函数() => 实例
// 创建多个实例对象
var f1 = new Foo('zhangsan',21)// new 就是实例化的过程
var f2 = new Foo('lisi',22)
var f3 = new Foo('wangwu',23)
实例对象.__proto__ === 构造函数.prototype

实例对象.proto === 构造函数.prototype

描述 new 一个对象的过程
  • 创建一个新对象,它继承自构造函数Foo.prototype
  • 执行构造函数Foo。执行时,相应的参数会被传入,同时上下文 this,指向这个新实例。在不传递参数时,new Foo等同于new Foo()
  • 执行代码,即对 this 赋值(this.key = value)
  • 构造函数 Foo,return一个对象,那么这个对象会取代整个 new 出来的结果。如果构造函数没有return对象,那么 new 出来的结果,就是步骤1创建的新对象
3.原型对象:prototype,每个函数都有这个属性,也是用于扩展构造函数的
Foo.prototype.alertName = function(){
  alert(this.name)
}

f1.printName = function(){
  console.log(this.name)
}

f1.printName()
f1.alertName()

构造器:constructor

在原型对象(prototype)下有个constructor属性,它的作用是指向声明的那个函数(就是构造函数)

Foo.prototype.constructor === Foo返回true
原型5条规则:
  1. 所有的引用类型(对象、函数、数组),都具有对象特性,即可自由扩展属性(除null以外)
  2. 所有的引用类型(对象、函数、数组),都有一个__ proto __属性,属性值是一个普通对象(除null以外)—— 隐式原型
  3. 所有的函数都有一个prototype属性,属性值是一个普通对象 —— 显示原型
  4. 所有的引用类型(对象、函数、数组),__ proto __属性值指向(全等)它的构造函数的prototype属性值
  5. 当试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,那么会去它的__ proto __中寻找,(也就是去它的构造函数的 prototype)
4.原型链
var obj = { name: 'obj' } 

在我们没对obj进行任何操作之前,obj就已经有toString()方法,那么toString()方法是怎么来的哪?


答:这跟 proto 有关。

当我们「读取」 obj.toString 时,JS 引擎会做下面的事情:

  1. 看看 obj 对象本身有没有 toString 属性。没有就走到下一步。
  2. 看看 obj.__ proto __ 对象有没有 toString 属性,发现 obj.__ proto __ 有 toString 属性,于是找到了,就会退出
    所以 obj.toString 实际上就是第 2 步中找到的 obj.__ proto __.toString。
    可以想象,
  3. 如果 obj.__ proto __ 没有,那么浏览器会继续查看 obj.__ proto . proto __
  4. 如果 obj.__ proto . proto __ 也没有,那么浏览器会继续查看 obj.__ proto . proto . proto __
  5. 直到找到 toString 或者 __ proto __ 为 null。
    上面的过程,就是「读」属性的「搜索过程」。

而这个「搜索过程」,是连着由 __ proto __ 组成的链子一直走的。

这个链子就叫原型链。

通过下图我们可以更好的理解:

典型例题:
function Fn(){
    this.a = function(){
        console.log(1)
    }
}
Fn.prototype.a = function(){
    console.log(2)
}
new Fn().a()  //1

上面的代码先从实例对象本身开始搜索有没有a这个方法,因为本身就有所以直接退出搜索
参考文章:什么是 JS 原型链?

你可能感兴趣的:(原型与原型链)