从构造函数到原型链

255 阅读10分钟

构造函数

new 操作符用于创建一个给定构造函数的实例对象

调用方式

构造函数通过 new 操作符调用,创建出来的实例对象可以访问构造函数中的属性,也可以访问原型链中的属性。

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

new 的过程

  1. 隐式的创建了一个新对象(obj),
  2. 将对象与构造函数通过原型链连接起来
  3. 将构造函数中的this绑定到新建的对象obj上
  4. 根据构造函数返回类型作判断,如果是原始值则被忽略仍然返回this,如果是返回对象,直接返回
function Person(name, age){
    this.name = name;
    this.age = age;
}
const person1 = new Person('Tom', 20)
console.log(person1)  // Person {name: "Tom", age: 20}
person1.sayName() // 'Tom'

实例创建的流程图:

构造函数的返回值

  • 没有 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('Tom',20)

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

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

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

手写实现 new

function myNew(Func,...args){
  //  1. 创建新对象
  let obj ={}

  // 2. 将新对象的原型与构造函数的原型对象进行关联
  obj.__proto__ = Func.prototype

  // 3.将构建函数的 this 指向新对象
	let result = Func.apply(obj,args)

  // 4. 判断构造函数的返回值类型
	return result instanceof Object ? result : obj
}


// ============= 测试代码 ====================

function Person(name, age) {
  this.name = name;
  this.age = age;
  return 1
}

Person.prototype.say = function () {
    console.log(this.name)
}

let p = myNew(Person, "huihui", 20)
console.log(p) // Person {name: "huihui", age: 20}
p.say() // huihui

普通函数

调用方式

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

返回值:

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

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

小结

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

原型

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

prototype(原型对象)

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

person1.__proto__ === Person.prototype

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

constrcutor

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

每个对象都能找到其对应的构造函数,这个 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()也是自己的实例

所有函数都是 Function 的实例(对象)

所有引用类型都继承了 Object,而这个继承也会通过原型链实现, 因此函数的默认原型都是 Object 的实例,默认原型都会包含一个内部指针,指向 Object.prototype。 所以函数的 __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() 的实例对象
  1. 构造函数作为实例对象时__proto__ 指向 Function 的原型(Function.prototype)
  2. Function的原型本身为一个对象,对象的__proto__ 指向原型:Object.prototype。
  3. Object() 作为构造函数时本身也是一个函数,

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

原型的判断方法

获取对象原型

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

hasOwnProperty()

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

Object.hasOwnProperty(propertyName) // true/false

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的__proto__指向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.prototypeObject.prototype

总结

  1. prototype 属性是函数所独有的;__proto__ 和 constructor 属性是对象所独有的。
    函数js中的一等公民,也是一种对象,所以函数也拥有 **proto** 和 constructor 属性。
  2. 原型对象 prototype用于存放实例公有属性和方法,原型对象都是Object()的实例。constructor 指向对象的构造函数。
  3. 原型链又叫隐式原型链,是由__proto__属性串联起来,

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

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

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

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

参考文章

  1. 【一张图理解js原型】www.javashuo.com/article/p-v…
  2. 【javascript原型等概念】www.javashuo.com/article/p-w…
  3. 【js中先有Function,还是先有Object】zhuanlan.zhihu.com/p/44724367