zoukankan      html  css  js  c++  java
  • Javascript: 从prototype漫谈到继承(1)

    本文同时发布在另一独立博客 Javascript: 从prototype漫谈到继承(1)

    javasscript的prototype原型链一直是一个难点,这篇文章是对自己这段时期学习的一个总结,在这里不谈ECMAScript标准,也不会用UML图画出各种关系(结合这两方面谈的文章非常的多,但大部分都相当晦涩,比如汤姆大叔),只力求最浅显易懂,深入浅出,供以后自己和各位参考。

    javascript的function一种对象(object),他们有方法和属性,方法比如call/apply,而prototype则是function的一个属性。

    一旦你定义了一个函数,它即自带了一个prototype属性

    function t(){};
    typeof t.prototype // "object";

    你可能已经知道使用函数作为一个构造函数,来生产一系列对象。比如

    function Some(name, color){
    this.name = name;
    this.color = color;
    this.method =function(){}
    }

    var a1 =new Some("Lee","black");//实例化一个对象

    上面的Some类的属性和方法也可以放在prototype对象中,比如

    function Some(){}
    Some.prototype.name ="Lee"//形式一

    Some.prototype ={ //形式二
    name:"lee", color:"black", method:function(){}
    }
    var a1 =newSome("Lee","black");//实例化一个对象

    虽然形式不同,但至少现在使用起来的效果是一致的。当你使用a.Lee或者a.method时,结果是一样的,现在还看不出分别


    Ok,那么第一点要注意的是,prototype是活着(live)的属性! 

    function Some(){}
    var a =newSome();
    a.method // undefined

    Some.prototype.method = function(){
    console.log("hello");
    } a.method
    // function () {console.log("Hello")}

    上面的代码想说明的是,在生成实例a时,构造函数没有method方法,所以a也没有,可以理解;但是之后构造函数在prototype属性里又添加上去了,虽然是在a生成之后添加的,但是a仍然照样拥有,与构造函数添加的时间无关。

    第二个问题来了,如果这个对象内部和prototype都定义了相同的字段怎么办,比如

    function Some(){
    this.color ="yellow";
    }
    Some.prototype.color ="black";
    var a =newSome();
    a.color //?

    上面的代码中,我在对象的内部和prototype上分别都定义了color,当我从实例中访问的时候,应该显示的是哪一个颜色?

    要注意的是第二点,javascript引擎首先会检查a的属性里有没有color,如果没有的话去它的构造函数的prototype(a.constructor.prototype)里有没有该属性

    让我们再看的远一点,任何一个对象都应该有自己的构造函数,函数的prototype属性也是个对象,那它的构造函数是什么?

    functionSome(){
    this.color ="yellow";
    }
    var a =newSome();
    a.constructor.prototype.constructor // function Some() {this.color = "yellow";} a.constructor.prototype.constructor.prototype // Some {}

    上面的原型链可以无限的追溯下去,通过原型链,可以追溯到最终的构造函数Object(),这也就解释了,为什么即使我们没有在函数上定义toString()函数,a.toString()的方法也是存在的,因为它最终调用的追溯到的Object的toString方法。

    新的问题是,如何区分自己的property和原型链上的属性,并且你能保证所有的属性都是可以访问的吗?

    众所周知,用for...in循环就可以解决这个问题,关于这个问题,只需要记住三点

    • 虽然在循环中对象自己的属性和原型链属性都会被列举出来,但并非所有属性都会被列举,比如一个数组的length和.splice之类的方法就不一定会被列举出来,可以列举出来的属性都是可枚举的(enumerable)
    • 如何区分对象自己的属性还是原型链的属性?使用hasOwnProperty()方法
    • 注意propertyIsEnumerable()方法,虽然该方法名字是“可枚举的属性”,但是原型链中所有的属性都会反悔false,即使是可枚举的

    还有一个对象的属性叫做__prop__,个人认为用处不大,只推荐在调试的时候使用,具体用法google去吧

    关于原型的继承

    如何写一个好的继承方法?这是一个逐渐演化过程,先从最简单的继承谈起

    function Parent(){
    this.deep ="Hello";
    }
    function Child(){
    this.shallow ="World";
    }
    Child.prototype =new Parent();
    var c =newChild();
    console.log(c.deep);

    当我们要访问c的deep属性时

    • 首先去c对象下查看有没有deep属性,没有
    • 再去c.construct.prototype对象的属性里查找,Parent的实例里查找,有

    但是上面的代码有一个问题,当你不断实例化Child时,Parent也不会被实例化,都会生成一个deep载入内存中,如果这个deep是共享的话,不如把deep放在prototype中

    function Parent(){}
    Parent.prototype.deep ="Hello";

    function Child(){
    this.shallow ="World";
    }
    Child.prototype =new Parent();
    var c =newChild();
    console.log(c.deep)
     

    当我们要访问c的deep属性时

    • 首先去c对象下查看有没有deep属性,没有
    • 再去c.construct.prototype对象的属性里查找,Parent的实例parent里查找,没有
    • 再去parent.construct.prototype查找deep,有

    这么做的弊端之一就是在查找某个属性的时候可能会多查找一轮

    让我们继续改进,我们发现我们需要的deep只在Parent的prototype上,那么其实我只需要Parent的prototype而不是Parent的实例

    function Parent(){}
    Parent.prototype.deep ="Hello";

    function Child(){
    this.shallow ="World";
    }

    Child.prototype = Parent.prototype;
    var c =new Child();
    console.log(c.deep);

    这样既避免了Parent的实例化,又避免了上一个例子中多一步的查找。但是有一个副作用,因为是对对象直接的引用,所以当Child.prototype.deep被修改时,Parent.prototype.deep也会被修改。那我们继续优化的目标就很明确了,要阻止这种对父类prototype的直接引用。

    于是我们决定使用一个中间变量

    function Parent(){}
    Parent.prototype.deep ="Hello";// 注意,来了var F =function(){}; F.prototype = Parent.prototype;

    function Child(){
    this.shallow ="World";
    }
    Child.prototype =
    new F();
    var c = new Child();
    console.log(c.deep)

    我们用F来作为一个中间变量,来阻止child对deep的修改可能影响parent

    当我们要访问c的deep属性时

    • 首先去c对象下查看有没有deep属性,没有
    • 再去c.construct.prototype对象的属性里查找,F的实例里查找,没有
    • 再去F.construct.prototype查找deep,有

    让我们来捋一捋为什么对Child.prototype的修改不会影响Parent.prototype

    • 在上一个例子中,我们对Child.prototype的操作就是对Parent.prototype的操作,无论读还是写,用的是别人的
    • 在这个例子中,Child.prototype不是对Parent的直接引用,而是一个新的空对象。在没有deep而我们需要deep时,被迫去Child.prototype的构造函数上去找,追溯到了Parent.protoype,而当我们需要写时,操纵的其实是Child.prototype = {}这个空对象。

    于是我们把最后一个代码片段抽象为一个方法

    function extend(Child,Parent){
    var F =function(){};
    F.prototype = Parent.prototype;
    Child.prototype = new F();
    // 一旦重置了函数的prototype,需要重新赋值prototype.constructor,
    // 忽略这方面的介绍

    Child.prototype.constructor = Child;

    // 保留对父类的引用,
    // 忽略对这方面的介绍
    Child.uber =Parent.prototype;
    }
  • 相关阅读:
    [oracle 使用(1)] win7/10 下Oracle10g的安装与卸载
    [原创]利用爬虫技术获取网页数据,以及想要的指定数据
    [mysql使用(1)] 64位Linux下安装mysql-5.7.13-linux-glibc2.5-x86_64
    aop 常见的问题
    Git技巧:右键菜单怎么去除?
    jrebel激活
    python与mysql连接
    linux下mysql的安装
    Linux下jdk和tomcat安装
    Lotto
  • 原文地址:https://www.cnblogs.com/hh54188/p/3059364.html
Copyright © 2011-2022 走看看