二、JavaScript的对象

为了能够清楚的解释这一切,我先从对象讲起。从其他面向对象语言(如Java)而来的人可能认为在JS里的对象也是由类来实例化出来的,并且是由属性和方法组成的。

实际上在JS里并不是如你所想(我开始是这么想的)那样,对象或直接称为object,实际上只是一些映射对的集合,像Map,字典等概念。JS里有大概7种类型(加上Symbol),数字、字符串、null、undefined、布尔、Symbol、对象。除对象以外的其他类型属于原始类型,就是说它们比较单纯,包含的东西比较少,基本上就是字面量所表示的那些(像C语言中的一些类型,就是占那么多空间,没有其他的东西)。object基本上是一些键值对的集合,属于引用类型,即是有一个名字去指向它来供别人使用的,就好像比较重的东西你拿不动,而只是拿了张记录东西所在地的纸条。所以当A对象里嵌套了B对象,仅表示A里面有一个引用指向了B,并不是真正把B包含在A里面,虽然看起来是这样(尤其是从对象的字面量上来看),所以才会有所谓的深拷贝与浅拷贝。

有句话叫“JavaScript里一切皆对象”,是因为在很多情况下原始类型会被自动的转为对象,而函数实际上也是对象,这样这句话看起来就很有道理了。

说明对象的本质是为了正确地认识对象,因为这关系到后面的理解。

三、原型也是对象

JS的世界里有一些对象叫原型,如果你有所怀疑,你可以在chrome终端下打出以下代码来验证它的存在:

console.log(Object.prototype); //你可以理解prototype是指向原型的引用

和 console.log(typeof Object.prototype);//object

在看看:

console.log(typeof {}.prototype);//undefined

为什么空对象{}没有prototype对象呢,事实上prototype只是函数对象的一个属性,而Array、Object却是都是函数,而不是对象或者类(class):

console.log(typeof Object);//function

四、函数,特殊的对象

为什么JS里没有函数这样一种类型,而typeof输出的却是function,即JS把函数也看成了一种类型,这揭示了函数作为一种特殊对象的地位的超然性。

function foo(){console.log('inner foo');};

console.log(typeof foo);//function

console.log(typeof []);//object

与数组这种内建对象相比,说明了函数的地位非比寻常,实际上函数在JS中地位是一等的(或者说大家是平等的),函数可以在参数中传递也说明了这一点,这使得JS具备了一些属于函数式语言的特性。

函数与普通对象的地位相等,使得函数中的"this"关键字极具迷惑性,可能很多人都知道了,this指向的是函数在运行时的上下文,既不是函数对象本身,也不是函数声明时所在作用域,具体是如何指向某个对象的就不在本文的讨论范畴了,感兴趣的可以去看《You-Dont-Know-JS》。

查看如下代码的输出结果:

console.log(foo.prototype);

可以看出foo.prototype是一个大概有两个属性的对象:constructor和__proto__。

console.log(foo.prototype.constructor === foo);//true

可以看出一个函数的原型的constructor属性指向的是函数本身,你可以换成内建的一些函数:Object、String、Number,都是这样的。

在观察foo.prototype的__proto__之前,先考察下面看起来很面向对象的几行代码:

var fooObj = new foo();//inner foo

console.log(fooObj);//看得到,fooObj也有一个__proto__的属性,那么__proto__是什么呢,

console.log(fooObj.__proto__ === foo.prototype);//true

你知道了,对象的__proto__会指向其“构造函数”的prototype(先称之为构造函数)。

new 的作用实际上是,新创建一个对象,在这个对象上调用new关键字后面的函数(this指向此对象,虽然这里没有用到),并将对象的__proto__指向了函数的原型,返回这个对象!

为了便于理解以上的内容,我画了这张图:

用绿色表明了重点:foo.prototype,同时函数声明可以这样声明:

var bar = new Function("console.log('inner bar');");

猜测console.log(foo.__proto__ === Function.prototype);输出为true;

的确如此,于是再向图片中加入一些东西:

看起来越来越复杂了,还是没有讲到foo.prototype的__proto__指向那里。

五、原型链的机制

如果把prototype对象看成是一个普通对象的话,那么依据上面得到的规律:

console.log(foo.prototype.__proto__ === Object.prototype);//true

是这样的,重新看一个更常见的例子:

 1 function Person(name){
2 this.name = name;
3 var label = 'Person';
4 }
5
6 Person.prototype.nickName = 'PersonPrototype';
7
8 var p1 = new Person('p1');
9
10 console.log(p1.name);//p1
11 console.log(p1.label);//undefined
12 console.log(p1.nickName);//PersonPrototype

先从图上来看一下上面这些对象的关系:

为什么p1.nickName会输出PersonPrototype,这是JS的内在的原型链机制,当访问一个对象的属性或方法时,JS会沿着__proto__指向的这条链路从下往上寻找,找不到就是undefined,这些原型链即图中彩色的线条。

六、面向对象的语法

把JS中面向对象的语法的内容放到靠后的位置,是为了不给读者造成更大的疑惑,因为只有明白了原型及原型链,这些语法的把戏你才能一目了然。

面向对象有三大特性:封装、继承、多态

封装即隐藏对象的一些私有的属性和方法,JS中通过设置对象的getter,setter方法来拦截你不想被访问到的属性或方法,具体有关对象的内部的东西限于篇幅就不再赘述。

继承是一个面向对象的语言看起来很有吸引力的特性,之前看一些文章所谓的JS实现继承的多种方式,只会使人更加陷入JS面向对象所造成的迷惑之中。

从原型链的机制出发来谈继承,加入Student要继承Person,那么应当使Sudent.prototype.__proto__指向Person.prototype。

所以借助于__proto__实现继承如下:

 1 function Person(name){
2 this.name = name;
3 var label = 'Person';
4 }
5
6 Person.prototype.nickName = 'PersonPrototype';
7
8 Person.prototype.greet = function(){
9 console.log('Hi! I am ' + this.name);
10 }
11
12 function Student(name,school){
13 this.name = name;
14 this.school = school;
15 var label = 'Student';
16 }
17
18 Student.prototype.__proto__ = Person.prototype;19
20 var p1 = new Person('p1');
21 var s1 = new Student('s1','USTB');
22 p1.greet();//Hi! I am p1
23 s1.greet();//Hi! I am s1

这时的原型链如图所示:

多态意味着同名方法的实现依据类型有所改变,在JS中只需要在“子类”Student的prototype定义同名方法即可,因为原型链是单向的,不会影响上层的原型。

1 Student.prototype.greet = function()
2 {
3 console.log('Hi! I am ' + this.name + ',my school is ' + this.school);
4 };
5 s1.greet();//Hi! I am s1,my school is USTB

为什么Student和Person的prototype会有constructor指向函数本身呢,这是为了当你访问p1.constructor时会指向Person函数,即构造器(不过没什么实际意义),还有一个极具迷惑性的运算符:instanceof,

instanceof从字面意上来说就是判断当前对象是否是后面的实例, 实际上其作用是判断一个函数的原型是否在对象的原型链上:

s1 instanceof Student;//true
s1 instanceof Person;//true
s1 instanceof Object;//true

ES6新增的语法使用了 class 和extends来使得你的代码更加的“面向对象”:

 1 class Person{
2 constructor(name){
3 this.name = name;
4 }
5
6 greet(){
7 console.log('Hello, I am ' + this.name);
8 }
9 }
10
11 class Student extends Person{
12 constructor(name, school){
13 super(name);
14 this.school = school;
15 }
16
17 greet(){
18 console.log('Hello, I am '+ this.name + ',my school is ' + this.school);
19 }
20 }
21
22 let p1 = new Person('p1');
23 let s1 = new Student('s1', 'USTB');
24 p1.greet();//Hello, I am p1
25 p1.constructor === Person;//true
26 s1 instanceof Student;//true
27 s1 instanceof Person;//true
28 s1.greet();//Hello, I am s1my school is USTB

super这个关键字用来引用“父类”的constructor函数,我是很怀疑这可能是上面所说的__proto__继承方式的语法糖,不过没有看过源码,并不清楚哈。

你肯定已经清楚地明白了JavaScript是如何“面向对象”的了,讽刺地讲,JavaScript不仅名字上带了Java,现在就连语法也要看起来像Java了,不过这种掩盖自身语言实现的真实特性,来伪装成面向对象的语法只会使得JavaScript更令人迷惑和难以排查错误。

JavaScript的面向对象原理之原型链的更多相关文章

  1. JavaScript的面向对象原理之原型链详解

    一.引言 在16年的10月份,在校内双选会找前端实习的时候,hr问了一个问题:JavaScript的面向对象理解吗?我张口就说“JavaScript是基于原型的!”.然后就没什么好说的了,hr可能不知 ...

  2. javascript 面向对象 new 关键字 原型链 构造函数

    JavaScript面向对象JavaScript 语言使用构造函数(constructor)作为对象的模板.所谓"构造函数",就是专门用来生成实例对象的函数.它就是对象的模板,描述 ...

  3. JavaScript中的继承(原型链)

    一.原型链 ECMAScript中将原型链作为实现继承的主要方法,基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法. 实例1: function SupType() { this.pro ...

  4. JavaScript继承基础讲解,原型链、借用构造函数、混合模式、原型式继承、寄生式继承、寄生组合式继承

    说好的讲解JavaScript继承,可是迟迟到现在讲解.废话不多说,直接进入正题. 既然你想了解继承,证明你对JavaScript面向对象已经有一定的了解,如还有什么不理解的可以参考<面向对象J ...

  5. 《JAVASCRIPT高级程序设计》根植于原型链的继承

    继承是面向对象的语言中,一个最为津津乐道并乐此不疲的话题之一.JAVASCRIPT中的继承,主要是依靠原型链来实现的.上一篇文章介绍过,JAVASCRIPT中,每一个对象都有一个prototype属性 ...

  6. JavaScript高级内容笔记:原型链、继承、执行上下文、作用域链、闭包

    最近在系统的学习JS深层次内容,并稍微整理了一下,作为备忘和后期复习,这里分享给大家,希望对大家有所帮助.如有错误请留言指正,tks. 了解这些问题,我先一步步来看,先从稍微浅显内容说起,然后引出这些 ...

  7. JavaScript 变量、函数与原型链

    定义 || 赋值 1-函数的定义 函数定义的两种方式: “定义式”函数:function fn(){ alert("哟,哟!"); } “赋值式”函数:var fn = funct ...

  8. JavaScript系列:再巩固-原型链

    图 1.实例:'对象(实例)'有属性__proto__,指向该对象(实例)的'构造函数的原型对象'. 2.方法:'构造函数'除了有属性__proto__,所有构造函数方法的__proto__指向Fun ...

  9. 深入理解JavaScript中的继承:原型链篇

    一.何为原型链 原型是一个对象,当我调用一个对象的方法时,如果该方法没有在对象里面,就会从对象的原型去寻找.JavaScript就是通过层层的原型,形成原型链. 二.谁拥有原型 任何对象都可以有原型, ...

随机推荐

  1. TCP使用注意事项总结

    目录 发送或者接受数据过程中对端可能发生的情况汇总 本端TCP发送数据时对端进程已经崩溃 本端TCP发送数据时对端主机已经崩溃 本端TCP发送数据时对端主机已经关机 某个连接长时间没有数据流动 TCP ...

  2. lock和synchronized如何选择?

    1.lock是一个接口,而synchronized是java关键字,synchronized是内置的语言实现. 2.synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁,而l ...

  3. Hexo+NexT(二):Hexo站点配置详解

    阅读本篇之前,假定读者已经有了Node.js的基础,如需要补充Node.js知识的,请自行百度. Hexo是在Node.js框架下的一个项目,利用Node.js提供的强大功能,完成从Markdown到 ...

  4. TCP/IP 第四、五章

    1, 2, 整个arp请求的过程. 3,arp -a 获取arp高速缓存.一般arp高速缓存存活时间20分钟,不完整的表项设置为3分钟.因为机器的ip地址可能发生改变. 4, 5,arp一般是操作系统 ...

  5. 如何在VPS上搭建WordPress博客网站(史上最全图文教程)

    由于现在很多人仍然使用共享主机,所以我决定写这篇教程,教你如何设置自己的虚拟专用服务器(VPS),以便为启动一个 WordPress 网站准备好所有必要的服务. 为什么共享托管不是最好的选择? 你的 ...

  6. CDH 5.15.2 离线安装

    一.前置准备 1. 基础信息 1.1 机器 机器名 服务 hadoop1 主节点 hadoop2 data.task hadoop3 data.task 1.2 服务版本 服务 版本 cdh 5.15 ...

  7. 微服务-springboot打包

    idea打包方式: 打包前确认项目可以正常运行 一.File->Project Structure->Artifacts->点击 + ->JAR->From module ...

  8. 50行Python代码,教你获取公众号全部文章

    > 本文首发自公众号:python3xxx 爬取公众号的方式常见的有两种 - 通过搜狗搜索去获取,缺点是只能获取最新的十条推送文章 - 通过微信公众号的素材管理,获取公众号文章.缺点是需要申请自 ...

  9. ElasticStack学习(五):ElasticSearch索引与分词

    一.正排索引与倒排索引 1.什么是正排索引呢? 以一本书为例,一般在书的开始都会有书的目录,目录里面列举了一本书有哪些章节,大概有哪些内容,以及所对应的页码数.这样,我们在查找一些内容时,就可以通过目 ...

  10. vim与系统剪切板之间的复制粘贴

    背景 vim各种快捷建溜得飞起,然而与系统剪切板之间的复制粘贴一直都是我的痛. 每次需要从vim中拷贝些文字去浏览器搜索,都需要用鼠标选中vim的文字后,Ctrl+c.Ctrl+v,硬生生掐断了纯键盘 ...