从构造函数到原型链

构造函数

调用方式

构造函数通过 new 操作符调用,背地里创建一个连接到该函数的 prototype 成员的新对象,同时 this 会被绑定到新对象上,并将该对象作为返回值返回,该对象有自己的 this,通过对象调用构造函数中的方法或属性。

new 的过程

  1. 隐式的创建了一个新对象(obj),
  2. 将构造函数的作用域赋值给该对象( this 指向新对象)
  3. 执行构造函数的代码,将属性方法赋值给该对象,
  4. 返回新对象。

返回值

  • 没有 return 默认返回 新创建的对象。
  • 若 return 基础数据类型, 无效,仍然将 this 返回。
  • 若 return 引用数据类型,则返回引用数据类型地址。
function Person(name,age){
  this.name = name 
  this.age = age
  this.sayName = function(){
    console.log('my name is ',this.name)
  }
}
// 通过 new  实例化一个 person 对象
const person1 = new Person('yiyi',18)

// 构造函数的函数名与类名相同,即是函数名又是类名
console.log(person1.constructor === Person )   //  ====>> true 

// 用instanceof 可以检查一个对象是否是一个类的实例
console.log(person1 instanceof Person)  //  ====>> true 

// 所有对象都是Object对象的后代,所以任何对象和Object做instanceof都会返回true
console.log(person1 instanceof Object)  //  ====>> true

普通函数

调用方式

可直接调用,由 window 调用,没有自己的 this

返回值:

  • 没有return 默认return undefined
  • 有 return 则返回函数体中 return 的内容
function person {
  console.log('yiyi')
}

// 直接调用,this 指向 window 
person()

小结

函数在定义时是无法区分是否为构造函数, 只有在调用时才可以区分出来
任何函数只要通过 new 操作符调用的就可作为构造函数
构造函数首字母大写,普通函数采用驼峰命名

原型

每个对象都有__proto__属性,但只有函数对象才有 prototype 属性

prototype(原型对象)

每个函数都有一个 prototype 属性,该属性为一个指针,指向原型对象,该对象用于存放所有实例共有的属性和方法
image.png

person1.__proto__ === Person.prototype

// 返回对象的原型
Object.getPrototypeOf(person1) === Person.prototype

constrcutor

所有的原型对象都会自动获得一个 constructor(构造函数)属性,这个属性(是一个指针)指向 prototype 属性所在的构造函数(Person)。

image.png
每个对象都能找到其对应的构造函数,这个 constructor 可能是对象自己本身显式定义的或者通过__proto__在原型链中找到的。
通过函数 new 创建的实例对象即使自己没有constructor 属性,它也能通过__proto__找到对应的 prototype上的 constructor,所以任何对象最终都可以找到其构造函数

Person.prototype.constructor === Person

// person1 本身并不具备 constructor 属性,通过原型链继承而来
person1.constructor === Person
person1.hasOwnProperty('constructor')  //  false

person1.constructor === Person.prototype.constructor

__proto__

每个实例化对象中都有一个隐藏的 **__proto__**属性,该属性指 向构造函数的原型对象(即父类对象)。实例与构造函数之间没有直接的关系,通过__proto__ 与原型对象创建关系


虽然实例中不包含属性和方法, 但通过原型链的查找实现了该功能

原型链 ( __proto__与prototype)

每个对象通过__proto__属性指向它的原型对象,这个原型对象又有自己的原型,直到某个对象的原型为 null 为止,这种一级一级的链结构就称为原型链。

  • 原型就是我们的prototype
  • 链就是__proto__,它让整个链路连接起来。
显示原型

利用函数上的prototype属性查找原型。

隐式原型

利用对象上的__proto__属性查找原型,这个属性指向当前对象的构造函数的原型对象,

__proto__这个属性是对象上的属性,所以可以在实例对象上面使用。

当读取实例的属性时,如果找不到,就会查找与对象关联的原型中的属性,如果还查不到,就去找原型的原型,一直找到最顶层Object为止。

Object是JS中所有对象数据类型的基类(最顶层的类),Object.prototype._ proto_ === null

由上图可知存在三个构造函数 Person()、 Function()、 Object()
函数皆由 Function() 构造而来,Function()本身也是函数,所以Function()也是自己的实例
所以函数的 __proto__指向了 函数的原型Function.prototype。
构造函数实例化的对象上存在__proto__属性并指向 Object.prototype。

Person.prototype = function.prototype

function.prototype.__proto__ = Object.prototype

object.prototype.__proto__ =null  // object 原型的尽头是null

Person() 构造函数是由函数而来函数的 prototype 指向 function.prototype (函数的原型)
所以由此得出一下结论:

//  原型对象也是对象,所以它也有原型即 Object.prototype
Person.prototype.__proto__ === Object.prototype
  1. Person 函数是属于 Function 这个构造函数的,由Function 构造而来Person 的,为 Function() 的实例对象
  2. 构造函数作为实例对象时__proto__指向 Function 的原型(Function.prototype)
  3. Function的原型本身为一个对象,对象的__proto__指向原型:Object.prototype。
  4. Object() 作为构造函数时本身也是一个函数,

相应地,构造函数 Object() 的 prototype 属 性指向原型对象Object.prototype;
实例对象 Person.prototype 的 __proto__ 属性一样指向原型对象 Object.prototype。

除了使用.__proto__ 方式访问对象的原型,还能够经过 Object.getPrototypeOf 方法来获取对象的原型 ,以及经过 Object.setPrototypeOf 方法来重写对象的原型。
我们可以使用对象的 .__proto__来检查对象自身中是否含有该属性;使用in检查对象中是否含有某个属性时,如果对象中没有但是原型中有,也会返回true

原型的判断方法

获取对象原型

除了使用 .proto 方式访问对象的原型,还能通过 Object.getPrototypeOf 方法来获取对象的原型 ,以及 Object.setPrototypeOf 方法来重写对象的原型。

hasOwnProperty()

我们可以使用对象的 hasOwnProperty() 来检查对象自身中是否含有该属性,hasOwnProperty()方法存在于原型对象中的__proto__中。

in()

使用 in 检查对象中是否含有某个属性时,会沿着原型链查找,如果对象中没有但是原型中有,也会返回true。

instanceof

我们常用 typeof 来判断数据的类型,但是typeof 只能用于判断基础数据类型,当判断引用数据类型的时无论什么类型的变量,都会返回 Object。
所以这里引入了 instanceof 来判断引用数据类型。
instanceof 也可以用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。
通过判断参数对象的原型链上是否能找到构造函数的 prototype,来确定 instanceof 的返回值。
实现 instanceof 方法,有三种:

  1. Object.getPrototypeOf(obj)——返回指定对象的原型(内部 [[Prototype]] 属性的值)
  2. Object.prototype.isPrototypeOf(obj)——测试一个对象是否存在于另一个对象的原型上
  3. obj.proto——使用非标准的 proto 的伪属性
  4. 使用 Object.getPrototypeOf(obj)
function instance_of(o, c) {
  let op = Object.getPrototypeOf(o);
  const cp = c.prototype;

  while(true) {
    if (!op) {
      return false
    } 

    if (op === cp) {
      return true
    }

    op = Object.getPrototypeOf(op);
  }
}
  1. Object.prototype.isPrototypeOf(obj)
function instance_of(o, c) {
  return c.prototype.isPrototypeOf(o);
}
  1. obj.proto
function instance_of(o, c) {
  let op = o.__proto__;
  const cp = c.prototype;

  while(true) {
    if (op === null) {
      return false
    }

    if (op === cp) {
      return true
    }

    op = op.__proto__;
  }
}

万物起源 null

一切皆为对象,却不知,JavaScript的世界中的对象,追根溯源来自于一个 null。

null 也是做为一个对象存在,基于它继承的子子孙孙,当属对象。乍一看,null 像是上帝,而 Object 和 Function 犹如JavaScript世界中的亚当与夏娃
顺着原型链查找: person1_ —> _Person.prototype —> Object.prototype —> null

Object.prototype 的原型指向null,由 null 作为原型链的终点,
null 在某种意义上来说也是一种对象,为表示一个为“空”的对象。

person1.__proto__ === Person.prototype
person1.__proto__.constructor === Person
Person.prototype.__proto__ === Object.prototype  // 原型对象的原型链 指向 原型对象
person1.__proto__.__proto__=== Object.prototype

Person.__proto__ === Function.prototype  // 函数对象的原型指向函数的原型对象
Function._ proto_ === Function.prototype // Function的原型指向函数原型对象
Object._ proto_ === Function.prototype //Object的原型指向函数原型对象
Function.prototype._ proto_ === Object.prototype 
//Function的原型对象的原型指向Object的原型对象
 

person1.__proto__ === Person.prototype   //person1的原型指向构造函数Person的原型对象
Person.prototype._ proto_ === Object.prototype  //Person的原型对象的原型指向Object的原型对象
Object.prototype._ proto_ === null   //Object的原型对象的原型指向null【原型链顶层】

Object 、Function

Function 对象比较特殊,它的构造函数就是它自己

Function 既可以看成是一个函数,也可以是一个对象,所有函数和对象最终都是由 Function 构造函数得来,任何函数均可以看作是经过 Function() 构造函数的new实例化的结果。

// Function类本身也是Function的一个实例
Function.prototype === Function.__proto__

// 它是它自己的构造函数
Function === Function.constructor

//Function 的本质是个对象 函数的prototype属性,自己是一个由Object构造的实例对象
Function.prototype.__proto__ === Object.prototype

// 函数和对象最终都是由 Function 构造函数得来
Object.__proto__ === Function.prototype  
// Object 本身是Function 的一个实例

以下四者绝对相等

  • Funtion.prototype (Function的__proto__指向其构造函数Function的prototype)
  • Function.__proto__ ( Function() 本身是由Function构造而来 )
  • Object.__proto__ (Object() 函数对象,本身是Function 的实例,由Function构造而来)
  • Person.__proto__ ( Person() 构造函数本身是函数 是 Function 的一个实例)
Object.__proto__ === Function.prototype  
Function.__proto__ === Function.prototype

若是把函数Person当成实例对象的话,其构造函数是Function(),其原型对象是Function.prototype;相似地,函数Object的构造函数也是Function(),其原型对象是Function.prototype。

Object作为一个构造函数(是一个函数对象),所以他的__proto__指向Function.prototype;

Function.prototype.__proto__ === Object.prototype

而 Function.prototype (原型对象)本质为一个对象,对象的__proto__ 为 Object.prototype,
所有原型对象的原型链最终都指向Object.prototype,而Object.prototype的prototype指向null(尽头);

先有鸡还是先有蛋

Function.prototype.__proto__ === Object.prototype, 可知 Function.prototype 是一个 Object 实例,那么应当是先有 Object 再有 Function。
可是Object.__proto__ === Function.prototype。 这样看来,没有 Function,Object也不能建立实例。
这就产生了一种「先有鸡仍是先有蛋」的经典问题,究竟是先有的 Object 还是先有的 Function 呢?

自盘古开天辟地,js中并不是就有了Object,而是Object.prototype。
js中的万物(原始值除外)都是继承自Object,唯独一个对象例外,那就是Object.prototype。

Object  instanceof Object;  //true
Object.prototype  instanceof Object; // false

全局下的 Object 构造自 Function.prototype,而 Function.prototype 构造自 Object.prototype。

Object.getPrototypeOf(Object) === Function.prototype   
Object.getPrototypeOf(Function.prototype) === Object.prototype

所以,是先有的Object.prototype,再有的 Function.prototype,再有的 Function 和 Object。

产生步骤:

  1. Object.prototype先于Object出现
  2. 然后用这个 prototype 构造 Function.prototype
  3. 有了 Function.prototype 再构造出 Function、Object 这几个构造器
  4. 然后把 Object.prototype 挂到 Object 上,Function.prototype 挂到 Function上

伪代码大致是这样,create元操作的含义是使用给定的对象作为原型构造一个新的对象。

var ObjectPrototype = create( );   // 开天辟地
 
var FunctionPrototype = create( ObjectPrototype );   
//FunctionPrototype(后被赋值给了Function.prototype)是Object类型的
//因为其原型是ObjectPrototype
 
var Function = create( FunctionPrototype );
 
Function.prototype = FunctionPrototype;
 // Function是Function类型的,也是Object类型的
//言外之意,Function对象 原型链上有Function.prototype和Object.prototype
 
Object =  create( FunctionPrototype ); 
 
Object.prototype = ObjectPrototype;
 //Object是Function类型的,也是Object类型的
//言外之意Object对象的原型链上有Function.prototype和Object.prototype 
 Function对象、Object对象的原型链上有Function.prototype和Object.prototype

总结

  1. prototype 属性是函数所独有的;__proto__ 和 constructor 属性是对象所独有的。
    函数js中的一等公民,也是一种对象,所以函数也拥有 __proto__和 constructor 属性。

  2. 原型对象 prototype用于存放实例公有属性和方法,原型对象都是Object()的实例。constructor 指向对象的构造函数。

  3. 原型链又叫隐式原型链,是由__proto__属性串联起来,

    原型链的尽头是 Object.prototype.proto==>null

  4. 构造函数是使用了new关键字的函数,用来创建对象,所有函数都是Function()的实例

    所以函数(此时看成了对象)的构造函数都指向 Function。

  5. Object 构造自 Function.prototype,而 Function.prototype 构造自Object.prototype

参考文章

  1. 【一张图理解js原型】http://www.javashuo.com/article/p-vpjbjgnw-cd.html
  2. 【javascript原型等概念】http://www.javashuo.com/article/p-wpvyqmmr-ck.html
  3. 【js中先有Function,还是先有Object】https://zhuanlan.zhihu.com/p/44724367
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值