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 相关文章推荐
List all the Databases on a SQL Server
Jun 21 Javascript
前台js改变Session的值(用ajax实现)
Dec 28 Javascript
jquery.bgiframe.js在IE9下提示INVALID_CHARACTER_ERR错误
Jan 11 Javascript
jquery实现的仿天猫侧导航tab切换效果
Aug 24 Javascript
JavaScript判断FileUpload控件上传文件类型
Sep 28 Javascript
AngularJS 实现按需异步加载实例代码
Oct 18 Javascript
结合代码图文讲解JavaScript中的作用域与作用域链
Jul 05 Javascript
JavaScript中的子窗口与父窗口的互相调用问题
Feb 08 Javascript
bootstrap table 数据表格行内修改的实现代码
Feb 13 Javascript
详解vue-cli与webpack结合如何处理静态资源
Sep 19 Javascript
js统计页面上每个标签的数量实例代码
May 29 Javascript
解决Antd Table表头加Icon和气泡提示的坑
Nov 17 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
使用 eAccelerator加速PHP代码的目的
2007/03/16 PHP
PHP文件缓存类示例分享
2015/01/30 PHP
php中使用sftp教程
2015/03/30 PHP
PHP实现的方程求解示例分析
2016/11/11 PHP
CakePHP框架Model关联对象用法分析
2017/08/04 PHP
JQuery1.4+ Ajax IE8 内存泄漏问题
2010/10/15 Javascript
jQuery常见开发技巧详细整理
2013/01/02 Javascript
用jquery中插件dialog实现弹框效果实例代码
2013/11/15 Javascript
AngularJS实现textarea记录只能输入规定数量的字符并显示
2016/04/26 Javascript
js获取html的span标签的值方法(超简单)
2016/07/26 Javascript
详解nodejs 文本操作模块-fs模块(二)
2016/12/22 NodeJs
js实现数组去重方法及效率?Ρ? target=
2017/02/14 Javascript
js鼠标跟随运动效果
2017/03/11 Javascript
js实现双色球效果
2020/08/02 Javascript
解决vue请求接口第一次成功,第二次失败问题
2020/09/08 Javascript
Openlayers实现点闪烁扩散效果
2020/09/24 Javascript
[02:10]探秘浦东源深体育馆 DOTA2 Supermajor不见不散
2018/05/17 DOTA
[01:12:27]EG vs Secret 2018国际邀请赛淘汰赛BO3 第二场 8.22
2018/08/23 DOTA
Python MD5文件生成码
2009/01/12 Python
使用scrapy实现爬网站例子和实现网络爬虫(蜘蛛)的步骤
2014/01/23 Python
python 内置模块详解
2019/01/01 Python
python中with语句结合上下文管理器操作详解
2019/12/19 Python
Python3.7实现验证码登录方式代码实例
2020/02/14 Python
HTML5 新事件 小结
2009/07/16 HTML / CSS
加拿大户外探险购物网站:SAIL
2020/06/27 全球购物
写自荐信的七个技巧
2013/10/15 职场文书
会计毕业生自我鉴定
2013/11/04 职场文书
违反交通法规检讨书
2014/09/10 职场文书
工作粗心大意检讨书
2014/09/18 职场文书
监考失职检讨书
2015/01/26 职场文书
离婚答辩状范文
2015/05/22 职场文书
2016年综治和平安建设宣传月活动总结
2016/04/01 职场文书
MySQL连接查询你真的学会了吗?
2021/06/02 MySQL
Python基于百度AI实现抓取表情包
2021/06/27 Python
基于PostgreSQL/openGauss 的分布式数据库解决方案
2021/12/06 PostgreSQL
SpringBoot集成MongoDB实现文件上传的步骤
2022/04/18 MongoDB