二、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. shell多线程

    a='a1' b='b1' c='c1' d='d1' e='e1' ARR=($a $b $c $d $e) rd=`date +%N` for i in ${ARR[*]} do { ;>& ...

  2. springboot读取本地项目文件

    在读取springBoot+gradle构建的项目时,如果使用传统的FileInputStream读取文件流或者ResourceUtils工具类的方式 File file= ResourceUtils ...

  3. 01 Javascript简介(了解)

    Web前端有三层: HTML:从语义的角度,描述页面结构 CSS:从审美的角度,描述样式(美化页面) JavaScript:从交互的角度,描述行为(提升用户体验) JavaScript历史背景介绍 布 ...

  4. vsphere网络

    物理网络 物理机间建立的网络,VMware ESXi运行于物理机之上 虚拟网络 单台物理机上运行的虚拟机之间通信形成的逻辑网络. 一.网络概述 1. 物理以太网交换机 2.vSphere标准交换机 虚 ...

  5. Spring事务原理一探

    概括来讲,事务是一个由有限操作集合组成的逻辑单元.事务操作包含两个目的,数据 一致以及操作隔离.数据一致是指事务提交时保证事务内的所有操作都成功完成,并且 更改永久生效:事务回滚时,保证能够恢复到事务 ...

  6. python算法与数据结构-数据结构中常用树的介绍(45)

    一.树的定义 树是一种非线性的数据结构,是由n(n >=0)个结点组成的有限集合.如果n==0,树为空树.如果n>0,树有一个特定的结点,根结点根结点只有直接后继,没有直接前驱.除根结点以 ...

  7. Ruby字符串(1):String基本用法

    String字符串 字符串由String类提供,除了直接使用单双引号或其它字面量创建字符串,也可以使用String.new()方法来创建. a = "hello" b = Stri ...

  8. Fiddler如何自动修改请求和响应包

    Charles的Map功能可以将某个请求进行重定向,用重定向的内容响应请求的内容.这个功能非常方便.在抓包过程当中,有时候为了调试方便,需要将线上的服务定位到内网.比如我们线上的服务器域名为 api. ...

  9. LeetCode刷题------------------------------LeetCode使用介绍

    临近毕业了,对技术有种热爱的我也快步入码农行业了,以前虽然在学校的ACM学习过一些算法,什么大数的阶乘,dp,背包等,但是现在早就忘在脑袋后了,哈哈,原谅我是一枚菜鸡,为了锻炼编程能力还是去刷刷Lee ...

  10. .NET分布式框架 | Orleans 知多少

    引言 公司物联网项目集成Orleans以支持高并发的分布式业务,对于Orleans也是第一次接触,本文就分享下个人对Orleans的理解. 这里先抛出自己的观点:Orleans 是一个支持有状态云生应 ...