JavaScript中的原型prototype完全解析


Posted in Javascript onMay 10, 2016

   要理解JS中的prototype, 首先必须弄清楚以下几个概念
   1. JS中所有的东西都是对象

   2. JS中所有的东西都由Object衍生而来, 即所有东西原型链的终点指向Object.prototype
 

// ["constructor", "toString", "toLocaleString", "valueOf", "hasOwnProperty", "isPrototypeOf", 
   // "propertyIsEnumerable", "__defineGetter__", "__lookupGetter__", "__defineSetter__",
   // "__lookupSetter__"]
   console.log(Object.getOwnPropertyNames(Object.prototype));

   3. JS中构造函数和实例(对象)之间的微妙关系
   构造函数通过定义prototype来约定其实例的规格, 再通过 new 来构造出实例, 他们的作用就是生产对象.
   而构造函数(方法)本身又是方法(Function)的实例, 因此也可以查到它的__proto__(原型链)

   Object       / function F() {} 这样子的就是构造函数啦, 一个是JS原生API提供的, 一个是自定义的
   new Object() / new F()           这样子的就是实例啦
   实例就"只能"查看__proto__来得知自己是基于什么prototype被制造出来的,
   而"不能"再重新定义实例的prototype妄想创造出实例的实例了.

   实践出真知, 只有自己动手观察/思考才能真正领悟:

// 先来看看构造函数到底是什么
  // function Empty() {}  function Empty() {}
  console.log(Function.prototype, Function.__proto__);
  // Object {}          function Empty() {}
  console.log(Object.prototype, Object.__proto__);
  function F() {}
  // F {}              function Empty() {}
  console.log(F.prototype, F.__proto__);

   你可能已经晕了, 我们来分解一下。

prototype
   prototype输出的格式为: 构造函数名 原型
   首先看下Object.prototype输出了什么?
   Object {} -> 前面的Object为构造函数的名称, 后面的那个表示原型, 这里是一个{}, 即一个Object对象的实例(空对象)
   那么 F {} 我们就明白是什么意思了, F 就是构造函数的名称, 原型也是一个空对象

// 再来看看由构造函数构造出来的实例
  var o = new Object(); // var o = {};
  // undefined       Object {}
  console.log(o.prototype, o.__proto__);
  function F() {}
  var i = new F();
  // undefined       F {}
  console.log(i.prototype, i.__proto__);

   我们再深入一点, 定义下 F 的原型看看到底会发生些什么?

function F() {}
  F.prototype.a = function() {};
  var i = new F();
  // undefined       F {a: function}
  console.log(i.prototype, i.__proto__);

   这样我们就清楚的看到 i 是由 F 构造出来的, 原型是 {a: function}, 就是原本的空对象原型新增了一个 a 方法

   我们再换一种情况, 完全覆盖 F 的原型会怎么样?
   

function F() {}
  F.prototype = {
    a: function() {}
  };
  var i = new F();
  // undefined       Object {a: function}
  console.log(i.prototype, i.__proto__);

   咦~ 为什么这里表明 i 是由 Object 构造出来的? 不对吧!
   因为我们完全将 F 的prototype覆盖, 其实也就是将原型指定为对象{a: function}, 但这会造成原本的constructor信息丢失, 变成了对象{a: function}指定的constructor.
   那么对象{a: function}的constructor是什么呢?
   因为对象{a: function}其实就相对于

var o = {a: function() {}} // new了一个Object

   那么o的constructor当然是 Object 啦

   我们来纠正下这个错误

function F() {}
  F.prototype = {
    a: function() {}
  }
  // 重新指定正确的构造函数
  F.prototype.constructor = F;
  var i = new F();
  // undefined       F {a: function, constructor: function}
  console.log(i.prototype, i.__proto__);

   现在又能得到正确的原型信息了~

原型链

   然后来看看什么原型链又是个什么东西?
   简单的来讲和OOP中的继承关系(链)是一样的, 一层一层往上找, 直至最终的 Object.prototype

JavaScript中的原型prototype完全解析

      最最关键的是要弄清楚JS中哪些东西是(实例)对象, 这个简单了, JS中所有东西都是对象!
   再要弄清楚就是任何一个对象都是有一个原型的!

   那么我们来证明一下:
  

Object // 这是一个函数, 函数是 Function 的实例对象, 那么就是由 Function 构造出来的
  Object.__proto__ == Function.prototype // 那么Object的原型, true
  // 这个是一个普通对象了, 因此属于 Object 的实例
  Function.prototype.__proto__ == Object.prototype // true
  // 这已经是原型链的最顶层了, 因此最终的指向 null
  Object.prototype.__proto__ == null // true

  Function // 这也是一个函数, 没错吧!
  Function.__proto__ == Function.prototype // true
  
  function A() {} // 这是一个自定义的函数, 终归还是一个函数, 没错吧! 
  A.__proto__ == Function.prototype // 任何函数都是 Function 的实例, 因此A的原型是?
  var a = new A()
  a.__proto__ == A.prototype // 实例a是由A构造函数构造出来的, 因此a的原型是由A的prototype属性定义的
  A.prototype.__proto__ == Object.prototype // 普通对象都是 Object 的示例

Prototype和__proto__
每一个对象都包含一个__proto__,指向这个的对象的“原型”。
类似的事情是,每一个函数都包含一个prototype,这个prototype对象干什么的了?

咱们看看如下代码,用构造函数来创建一个对象(上面是用字面量的形式创建对象)。

function Foo(){};
var foo = new Foo();
console.log(foo.__proto__);

试想想,这个foo对象的__proto__会指向什么?

JavaScript中的原型prototype完全解析

一个包含constructor属性的对象?看不太懂没关系,把函数Foo的prototype属性打印出来,对比一下就知道了。

function Foo(){};
var foo = new Foo();
console.log(foo.__proto__);
console.log(Foo.prototype);
console.log(foo.__proto__ === Foo.prototype);

JavaScript中的原型prototype完全解析

原来,new出来的对象foo的__proto__就只指向函数Foo的prototype。

foo.__proto__ --> Foo.prototype

JS这么设计有何意义了?回忆下上面说的,在JS的世界中,对象不是根据类(模具)创建出来的,而是从原型(另一个对象)衍生出来的。

当我们执行new操作创建一个新的对象时,先不深入new操作的具体实现,但有一点我们是肯定的——就是为新对象的__proto__指向一个原型对象。

就刚才这段代码

function Foo(){};
var foo = new Foo();

foo.__proto__到底要指向谁了?你怎么不能指向Foo这个函数本身吧,虽然函数也是对象,这个有机会会详细讲。但如何foo.__proto__指向Foo固然不合适,因为Foo是一个函数,有很多逻辑代码,foo作为一个对象,继承逻辑处理没有任何意义,它要继承的是“原型对象”的属性。

所以,每个函数会自动生成一个prototype对象,由这个函数new出来的对象的__proto__就指向这个函数的prototype。

foo.__proto__ --> Foo.prototype

总结
说了这么多,感觉还是没完全说清楚,不如上一张图。我曾经参考过其他网友的图,但总觉得哪里没说清楚,所以我自己画了一张图,如果觉得我的不错,请点个赞!(老子可是费了牛劲才画出来)。

JavaScript中的原型prototype完全解析

咱们就着这张图,记住如下几个事实:

1. 每个对象中都有一个_proto_属性。

JS世界中没有类(模具)的概念,对象是从另一个对象(原型)衍生出来的,所以每个对象中会有一个_proto_属性指向它的原型对象。(参考左上角的那个用字面量形式定义的对象obj,它在内存中开辟了一个空间存放对象自身的属性,同时生成一个_proto_指向它的原型——顶层原型对象。)

2. 每个函数都有一个prototype属性。

“构造函数”为何叫构造函数,因为它要构造对象。那么根据上面第一条事实,构造出来的新对象的_proto_属性指向谁了?总不能指向构造函数自身,虽然它也是个对象,但你不希望新对象继承函数的属性与方法吧。所以,在每个构造函数都会有一个prototype属性,指向一个对象作为这个构造函数构造出来的新对象的原型。

3. 函数也是对象。

每个函数都有一些通用的属性和方法,比如apply()/call()等。但这些通用的方法是如何继承的呢?函数又是怎么创建出来的呢?试想想,一切皆对象,包括函数也是对象,而且是通过构造函数构造出来的对象。那么根据上面第二条事实,每个函数也会有_proto_指向它的构造函数的prototype。而这个构造函数的函数就是Function,JS中的所有函数都是由Function构造出来的。函数的通用属性与方法就存放在Function.prototype这个原型对象上。

Javascript 相关文章推荐
用于table内容排序
Jul 21 Javascript
php图像生成函数之间的区别分析
Dec 06 Javascript
JavaScript中的DSL元编程介绍
Mar 15 Javascript
JavaScript中ES6 Babel正确安装过程
Jul 18 Javascript
清空元素html("") innerHTML="" 与 empty()的区别和应用(推荐)
Aug 14 Javascript
canvas绘制爱心的几种方法总结(推荐)
Oct 31 Javascript
Node.js模块全局安装路径配置方法
May 17 Javascript
nodemon实现Typescript项目热更新的示例代码
Nov 19 Javascript
node.js中path路径模块的使用方法实例分析
Feb 13 Javascript
vue实现图片上传功能
May 28 Javascript
OpenLayers3实现测量功能
Sep 25 Javascript
如何使用原生Js实现随机点名详解
Jan 06 Javascript
简单解析JavaScript中的__proto__属性
May 10 #Javascript
Web Uploader文件上传插件使用详解
May 10 #Javascript
详解原生JavaScript实现jQuery中AJAX处理的方法
May 10 #Javascript
JS上传组件FileUpload自定义模板的使用方法
May 10 #Javascript
使用jQuery处理AJAX请求的基础学习教程
May 10 #Javascript
javascript关于继承解析
May 10 #Javascript
JavaScript继承学习笔记【新手必看】
May 10 #Javascript
You might like
php数组应用之比较两个时间的相减排序
2008/08/18 PHP
浅析ThinkPHP中execute和query方法的区别
2014/06/13 PHP
基于GD2图形库的PHP生成图片缩略图类代码分享
2015/02/08 PHP
thinkphp框架下实现登录、注册、找回密码功能
2016/04/06 PHP
使用php实现网站验证码功能【推荐】
2017/02/09 PHP
(转载)JavaScript中匿名函数,函数直接量和闭包
2007/05/08 Javascript
javascript 新浪背投广告实现代码
2009/07/07 Javascript
24款非常有用的 jQuery 插件分享
2011/04/06 Javascript
读jQuery之十一 添加事件核心方法
2011/07/31 Javascript
js replace替换所有匹配的字符串
2014/02/13 Javascript
JS组件Bootstrap导航条使用方法详解
2016/04/29 Javascript
js仿淘宝和百度文库的评分功能
2016/05/15 Javascript
JavaScript正则表达式exec/g实现多次循环用法示例
2017/01/17 Javascript
TableSort.js表格排序插件使用方法详解
2017/02/10 Javascript
bootstrap3使用bootstrap datetimepicker日期插件
2017/05/24 Javascript
微信分享调用jssdk实例
2017/06/08 Javascript
Vue.js组件间通信方式总结【推荐】
2018/11/23 Javascript
用Vue.js在浏览器中实现裁剪图像功能
2019/06/18 Javascript
Node.js控制台彩色输出的方法与原理实例详解
2019/12/01 Javascript
vue print.js打印支持Echarts图表操作
2020/11/13 Javascript
[52:32]完美世界DOTA2联赛PWL S2 Magma vs LBZS 第三场 11.18
2020/11/18 DOTA
[55:26]DOTA2-DPC中国联赛 正赛 Aster vs LBZS BO3 第一场 2月23日
2021/03/11 DOTA
在Python的Django框架中simple-todo工具的简单使用
2015/05/30 Python
使用Pytorch来拟合函数方式
2020/01/14 Python
大学生毕业自我评价范文分享
2013/11/11 职场文书
建筑工程技术应届生求职信
2013/11/17 职场文书
计算机专业毕业生的自我评价
2013/11/18 职场文书
机电一体化自荐信
2013/12/10 职场文书
外贸业务员求职信范文
2013/12/12 职场文书
大学班长的职责
2014/01/27 职场文书
常务副总经理岗位职责
2014/04/12 职场文书
小学三好学生事迹材料
2014/08/15 职场文书
九华山导游词
2015/02/03 职场文书
法院执行局工作总结
2015/08/11 职场文书
高三数学教学反思
2016/02/18 职场文书
企业内部管理控制:采购授权审批制度范本
2020/01/19 职场文书