原型对象 prototype

原型对象 prototype

包含:

  1. 函数和原型 (function & prototype)
  2. 对象和隐式原型 (object & __proto__)
  1. 原型和隐式原型 (prototype &  __proto__)
  2. 原型和构造器(prototype & constructor)
  1. 原型链 (prototype chain)
  2. 函数和对象间的关系(function & obj)

1.函数和原型 (function & prototype)

每一个函数都有一个属性 prototype,表示 "原型对象",以下简称原型。

1.1 原型 prototype 的来由

这个原型是怎么来的呢?

JS 不像其他静态语言,它没有"子类"和"父类"的概念,也没有"类"(class)和"实例"(instance)的区分,全靠一种很奇特的"原型链"模式,来实现继承。

这里就要提到当初 js 的创始人 Brendan Eich,他在创建 js 的时候遇到一个难题:要不要设计"继承"机制?

我们知道当初 Brendan Eich 在创建 JS 的时候,仅仅是想做简单的脚本语言以实现 "表单验证" 功能,那么它就无需 "继承" 机制,但 JS 里又必须有一种机制将所有对象联系起来。

最后, Brendan Eich 不打算引入 "类" 的概念,因为一旦有了 "类", JS 就变成了面向对象的编程语言了,会加大初学者的入门难度。因此他根据 C++ 和 Java 做了一个简化,把 new 命令引入到了 JS ,用来从原型对象生成一个实例对象,但在 JS 中 new 后面跟的不是一个类,而是一个构造函数。

回忆一下有见过哪些通过 new 出来的对象吗?类似于像  new Date( )new Array()new Set() 之类的都是由函数创建出来的对象。

例如前面的  Cat 的构造函数,就表示 "猫对象" 的原型(模版)。对 Cat 这个构造函数使用 new 关键字就会生成猫的实例 cat1、cat2... 等等。

在 JS 中对象的产生是通过原型对象而来,每一个对象,都有一个原型对象。而原型对象上面也有一个自己的原型对象,一层一层向上找,最终会到达 null

1.2 原型 prototype 的作用

我们常说 JS 是一门基于原型的语言,所以 JS 是通过函数,去模拟类。

回顾通过工厂模式和 class 的形式去封装类时,我们是如何去处理“方法共享”这个问题的?

没错,将方法放在 prototype 身上。所以 prototype 的作用其实就是 给所有实例提供公共访问

let obj = {
  name: "zhangsan"
};
console.log(obj.name, obj.age);	// zhangsan  undefined

Object.prototype.age = 18;
console.log(obj.name, obj.age);	// zhangsan 18

对象 obj 自身没有 age 属性,所以访问 obj.age 必定会得到 undefined。但 obj 可以看作通过 new Object() 创建的,或者说 obj 是 Object 类的实例,所以当在 Object.prototype 上放置 age 属性并且赋值后,实例对象自身没有的属性或值,通过访问创建该对象的类的 prototype,也能被访问到。

但通常,对象属性的值不会相同,比如每个人的姓名、年龄、性别。但方法却是相同或相似。所以为了不造成内存的浪费,只会将公共的方法放在类的 prototype 上。这也就是为什么我们自定义的 let students = [] 或者 let users = [] 这样的数组都能使用 push()length 之类的属性和方法了。

尝试在控制台打印 Array.prototype 你会发现数组所有的属性与方法。

函数对象与普通对象

2. 对象和隐式原型  (object & __proto__)

每一个实例对象都有一个 __proto__ 属性称为 隐式原型,指向创建该对象的构造函数的 prototype。这个属性本身也是对象。

let arr = [];
console.log(arr.__proto__);
console.log(Array.prototype);
console.log(arr.__proto__ == Array.prototype); // true

⚠️ 分清楚:

  • 函数的属性 prototype
  • 对象的属性 __proto__

3. 原型和隐式原型 (prototype &  __proto__)

console.log(arr.__proto__ === Array.prototype); 	// true

4. 原型和构造器(prototype & constructor)

每一个函数的 prototype 上有 constructor 属性,指向原型所在的类。

5. 原型链(prototype chain)

提到“链”,我们能想到关于链的:

  1. 链式调用:美化代码,语义化的调用以便于偷懒
  2. 作用域链:找变量
  1. 原型链:找公用的属性。方法属于特殊的属性,属性所对应的值是一个函数。(这也就是为什么一个自定义的数组在没有显式设置属性方法,却也能使用 .push() .length 等属性和方法的原因)

5.1 原型链的概念

一个实例对象,在调用属性或方法时,会依次从实例本身 --> 创建该实例对象的构造函数原型 --> Object.prototype ,查看是否有对应的属性或方法。这样的寻找方式就好像一个链条一样,从实例对象本身一直找到 Object.prototype ,专业上称之为原型链

function Dog() { }
Dog.prototype.eat = function () {
  console.log("eat meat!");
}
let dogObj = new Dog();
dogObj.eat();	// eat meat!

为什么实例对象 dogObj 自身没有 eat 方法,但调用 eat 方法却能打印?原因就在于 eat 方法是放在实例对象 dogObj 的构造函数 Dog 的原型 prototype 上的。在实例中没找到的方法,在构造函数的原型上找到了:

dogObj.__proto__ === Dog.prototype; 	// true
dogObj.__proto__.eat  === Dog.prototype.eat 	// true
// 实际调用的是:dogObj.__proto__.eat 也就是 Dog.prototype.eat

假设我需要调用一个 dogObj 没有的方法,Dog 也没有的方法呢?比如…toString()?

dogObj.toString();	// [object Object]
/*
	为什么执行成功?
	在实例 dogObj 中没有定义该方法
	在构造函数 Dog 的原型 prototype 上也没找到
	在构造函数的原型的原型 Object 上找到了
	实际调用的是 dogObj.__proto__.__proto__.toString 也就是 Object.prototype.toString
*/

由此可以看出 __proto__ 就像一个链条,串联起了实例对象和原型。

但同时一个新的问题出现,凭什么 Dog.prototype.__proto__ (或者说 obj.__proto__.__proto__)是 Object.prototype 呢?

5.2 推测实例的原型链

来看一个推测步骤:

  1. 先找到实例的 __proto__ 的上一个对象,即 Dog 类的 prototype

  2. 判断 Dog.prototype 的类型,typeof Dog.prototype // object
  1. 从上得出的结果是 object ,object 必定是由 Object 创建的,即:Dog.prototype.__proto__ === Object.prototype

5.3 推测函数的原型链

上面提到从实例推导出了 Object,那函数呢?Function.__proto__ 又是什么?

  1. 判断 Function 类型,typeof Function // function
  2. 从上得到结论:函数类型的构造函数就是 Function,或者说函数都是由 Function 创建的
  1. 所以 Function.__proto__ === Function.prototype 或者说 Dog.__proto__ === Function.prototype

同理可得所有可以通过 new 的构造函数的隐式原型,都是 Function 的原型:

  1. String.__proto__ === Function.prototype
  2. Number.__proto__ === Function.prototype
  1. Boolean.__proto__ === Function.prototype
  2. Date.__proto__ === Function.prototype
  1. Array.__proto__ === Function.prototype
  2. Object.__proto__ === Function.prototype
  1. Set.__proto__ === Function.prototype
  2. Map.__proto__ === Function.prototype
  1. RegExp.__proto__ === Function.prototype

5.4 原型链上不封顶?

刚才的推导里有一张图:

Object.prototype.__proto__ 该是谁?按照常理去推导,Object.prototype.__proto__ 是 Object.prototype,但这样下去原型链就在 Object 处无限循环了。为了解决这个问题,Brendan Eich 就直接规定了 Object.prototype.__proto__null,打破了原型链的无限循环。

这样就形成了一个链条称为原型链。原型链的顶端通往 null。

  • 本质上来说,所有 function 都可以看作是由 new Function()生成的,所有通过 new Function() 创建的对象叫做 函数对象
  • 所有对象都是由函数对象创建的。
  • 函数属于对象,函数可以创建对象。

5. 原型链(prototype chain)

let Fn = function () {}
Fn.prototype.talk = function () {console.log(123)}
let obj = new Fn();	
obj.talk();	// 实例对象 obj 自身是没有 talk 方法的,却可以通过 prototype 访问到构造函数的方法

6. 函数和对象间的关系 运算符 instanceof

instanceof,单词表示 “实例”、“XX之间的关系”,instanceof 用于判断一个对象 是否是 一个构造函数的 实例对象。原理就是利用了原型链。注意,instanceof 只能用于复杂数据类型。

所有对象 instanceof Object 都得到 true,因为 Object 在原型链的上端。

语法:obj instanceof class

function Student(){}
let obj = new Student();
console.log(obj instanceof Student);	// true

function Fn() {}
let obj = {};
console.log(obj instanceof Fn);	// false

function fn(){}
console.log(fn instanceof Object);	// true
console.log(fn instanceof Function);	// true

总结

  1. 原型:每个函数都有一个 prototype 属性叫做原型
  2. 隐式原型:每个对象都有一个 __proto__ 属性叫做隐式原型
  1. 所有对象本质上都可以看作是通过 Object 创建的
  2. 所有函数本质上都可以看作是通过 Function 创建的
  1. 判断  obj 是不是一个构造函数的实例对象 xxx.instanceof function
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值