• JavaScript的面向对象原理之原型链


    二、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更令人迷惑和难以排查错误。

  • 相关阅读:
    Round robin
    Linux命令之nslookup
    VLAN
    基础网络概念
    python开发_filecmp
    python开发_stat
    python开发_fileinput
    python开发_os.path
    python开发_bisect
    python开发_copy(浅拷贝|深拷贝)_博主推荐
  • 原文地址:https://www.cnblogs.com/lowerma/p/10998988.html
Copyright © 2020-2023  润新知