• JS中的prototype


    JS中的phototype是JS中比较难理解的一个部分

    本文基于下面几个知识点:

    1 原型法设计模式

    在.Net中可以使用clone()来实现原型法

    原型法的主要思想是,现在有1个类A,我想要创建一个类B,这个类是以A为原型的,并且能进行扩展。我们称B的原型为A。

    2 javascript的方法可以分为三类:

    a 类方法

    b 对象方法

    c 原型方法

    例子:

    复制代码
    function People(name)
    {
      this.name=name;
      //对象方法
      this.Introduce=function(){
        alert("My name is "+this.name);
      }
    }
    //类方法
    People.Run=function(){
      alert("I can run");
    }
    //原型方法
    People.prototype.IntroduceChinese=function(){
      alert("我的名字是"+this.name);
    }

     

    //测试

    var p1=new People("Windking");

    p1.Introduce();

    People.Run();

    p1.IntroduceChinese(); 
    复制代码

    3 obj1.func.call(obj)方法

    意思是将obj看成obj1,调用func方法

    好了,下面一个一个问题解决:

    prototype是什么含义?

    javascript中的每个对象都有prototype属性,Javascript中对象的prototype属性的解释是:返回对象类型原型的引用。

    A.prototype = new B();

    理解prototype不应把它和继承混淆。A的prototype为B的一个实例,可以理解A将B中的方法和属性全部克隆了一遍。A能使用B的方法和属性。这里强调的是克隆而不是继承。可以出现这种情况:A的prototype是B的实例,同时B的prototype也是A的实例。

    先看一个实验的例子:


    复制代码
    function baseClass()
    {
      this.showMsg = function()
      {
         alert("baseClass::showMsg");   
      }
    }

    function extendClass()
    {
    }

    extendClass.prototype = new baseClass();
    var instance = new extendClass();
    instance.showMsg(); // 显示baseClass::showMsg
    复制代码

    我们首先定义了baseClass类,然后我们要定义extentClass,但是我们打算以baseClass的一个实例为原型,来克隆的extendClass也同时包含showMsg这个对象方法。

    extendClass.prototype = new baseClass()就可以阅读为:extendClass是以baseClass的一个实例为原型克隆创建的。

    那么就会有一个问题,如果extendClass中本身包含有一个与baseClass的方法同名的方法会怎么样?

    下面是扩展实验2:


    复制代码
    function baseClass()
    {
        this.showMsg = function()
        {
            alert("baseClass::showMsg");   
        }
    }

    function extendClass()
    {
        this.showMsg =function ()
        {
            alert("extendClass::showMsg");
        }
    }

    extendClass.prototype = new baseClass();
    var instance = new extendClass();

    instance.showMsg();//显示extendClass::showMsg
    复制代码

    实验证明:函数运行时会先去本体的函数中去找,如果找到则运行,找不到则去prototype中寻找函数。或者可以理解为prototype不会克隆同名函数。

    那么又会有一个新的问题:

    如果我想使用extendClass的一个实例instance调用baseClass的对象方法showMsg怎么办?

    答案是可以使用call:


    复制代码
    extendClass.prototype = new baseClass();
    var instance = new extendClass();


    var baseinstance = new baseClass();
    baseinstance.showMsg.call(instance);//显示baseClass::showMsg
    复制代码

    这里的baseinstance.showMsg.call(instance);阅读为“将instance当做baseinstance来调用,调用它的对象方法showMsg”

    好了,这里可能有人会问,为什么不用baseClass.showMsg.call(instance);

    这就是对象方法和类方法的区别,我们想调用的是baseClass的对象方法

    最后,下面这个代码如果理解清晰,那么这篇文章说的就已经理解了:


    复制代码
    <script type="text/javascript">

    function baseClass()
    {
        this.showMsg = function()
        {
            alert("baseClass::showMsg");   
        }
       
        this.baseShowMsg = function()
        {
            alert("baseClass::baseShowMsg");
        }
    }
    baseClass.showMsg = function()
    {
        alert("baseClass::showMsg static");
    }

    function extendClass()
    {
        this.showMsg =function ()
        {
            alert("extendClass::showMsg");
        }
    }
    extendClass.showMsg = function()
    {
        alert("extendClass::showMsg static")
    }

    extendClass.prototype = new baseClass();
    var instance = new extendClass();

    instance.showMsg(); //显示extendClass::showMsg
    instance.baseShowMsg(); //显示baseClass::baseShowMsg
    instance.showMsg(); //显示extendClass::showMsg

    baseClass.showMsg.call(instance);//显示baseClass::showMsg static

    var baseinstance = new baseClass();
    baseinstance.showMsg.call(instance);//显示baseClass::showMsg

    </script>
    复制代码

    起由

    最近在做一个项目,里面大量地使用 javascript 作为页面的动态生成脚本, 使用 json 与服务器进行通信. 在读之前遗留的代码时, 经常会弄不清楚, 作用域, this关键字在当前context下的指向等,于是便开始专门学习了 相关的知识,记录下来与大家分享.

    下面的内容中会有一些代码,建议大家也去尝试修改和理解,这样更容易掌握. 点击 这儿 下载所涉及到的源码.

    prototype

    javascript 是一种 prototype based programming 的语言, 而与我们通常的 class based programming 有很大 的区别,我列举重要的几点如下:

    1. 函数是first class object, 也就是说函数与对象具有相同的语言地位
    2. 没有类,只有对象
    3. 函数也是一种对象,所谓的函数对象
    4. 对象是按 引用 来传递的

    那么这种 prototype based programming 的语言如何实现继承呢(OO的一大基本要素), 这也便是 prototype 的由来.

    看下面的代码片断:

    function foo(a, b, c)
    {
    return a*b*c;
    }
    alert(foo.length);
    alert(typeof foo.constructor);
    alert(typeof foo.call);
    alert(typeof foo.apply);
    alert(typeof foo.prototype);
    

    对于上面的代码,用浏览器运行后你会发现:

    1. length: 提供的是函数的参数个数
    2. prototype: 是一个object
    3. 其它三个都是function

    而对于任何一个函数的声明,它都将会具有上面所述的5个property(方法或者属性).

    下面我们主要看下prototype.

    // prototype
    function Person(name, gender)
    {
    this.name = name;
    this.gender = gender;
    this.whoAreYou = function(){//这个也是所谓的closure, 内部函数可以访问外部函数的变量
    var res = "I'm " + this.name + " and I'm a " + this.gender +".";
    return res;
    };
    }
    // 那么在由Person创建的对象便具有了下面的几个属性
    Person.prototype.age = 24;
    Person.prototype.getAge = function(){
    return this.age;
    };
    flag = true;
    if (flag)
    {
    var fun = new Person("Tower", "male");
    alert(fun.name);
    alert(fun.gender);
    alert(fun.whoAreYou());
    alert(fun.getAge());
    }
    Person.prototype.salary = 10000;
    Person.prototype.getSalary = function(){
    return this.name + " can earn about " + this.salary + "RMB each month." ;
    };
    // 下面就是最神奇的地方, 我们改变了Person的prototype,而这个改变是在创建fun之后
    // 而这个改变使得fun也具有了相同的属性和方法
    // 继承的意味即此
    if (flag)
    {
    alert(fun.getSalary());
    alert(fun.constructor.prototype.age);//而这个相当于你直接调用 Person.prototype.age
    alert(Person.prototype.age);
    }
    

    从上面的示例中我们可以发现,对于prototype的方法或者属性,我们可以 动态地 增加, 而由其创建的 对象自动会 继承 相关的方法和属性.

    另外,每个对象都有一个 constructor 属性,用于指向创建其的函数对象,如上例中的 fun.constructor 指向的 就是 Person.

    那么一个疑问就自然产生了, 函数对象中自身声明的方法和属性与prototype声明的对象有什么差别?

    有下面几个差别:

    1. 自身声明的方法和属性是 静态的, 也就是说你在声明后,试图再去增加新的方法或者修改已有的方法,并不会 对由其创建的对象产生影响, 也即 继承 失败
    2. 而prototype可以动态地增加新的方法或者修改已有的方法, 从而是 动态的 ,一旦 父函数对象 声明了相关 的prototype属性,由其创建的对象会 自动继承 这些prototype的属性.

    继续上面的例子:

    flag = true;
    // 函数内部声明的方法是静态的,无法传递的
    Person.school = "ISCAS";
    Person.whoAreYou = function(){
    return "zhutao";
    };//动态更改声明期的方法,并不会影响由其创建的对象的方法, 即所谓的 静态
    if (flag)
    {
    alert(Person.school);
    alert(fun.school);//输出的是 "undefined"
    alert(Person.whoAreYou()); //输出 zhutao
    alert(fun.whoAreYou()); // I'm Tower and I'm a male.
    }
    Person.prototype.getSalary = function(){
    return "I can earn 1000000 USD";
    };
    if (flag)
    {
    alert(fun.getSalary());//已经继承了改变, 即所谓的 动态
    }
    

    既然有函数对象本身的属性, 也有prototype的属性, 那么是由其创建的对象是如何搜索相应的属性的呢?

    基本是按照下面的流程和顺序来进行.

    1. 先去搜索函数对象本身的属性,如果找到立即执行
    2. 如果1没有找到,则会去搜索prototype属性,有2种结果,找到则直接执行,否则继续搜索 父对象 的 父对象 的prototype, 直至找到,或者到达 prototype chain 的结尾(结尾会是Object对象)

    上面也回答如果函数对象本身的属性与prototype属性相同(重名)时的解决方式, 函数本身的对象 优先 .

    再看一个多重prototype链的例子:

    // 多重prototype链的例子
    function Employee(name)
    {
    this.name = "";
    this.dept = "general";
    this.gender = "unknown";
    }
    function WorkerBee()
    {
    this.projects = [];
    this.hasCar = false;
    }
    WorkerBee.prototype = new Employee; // 第一层prototype链
    function Engineer()
    {
    this.dept = "engineer"; //覆盖了 "父对象"
    this.language = "javascript";
    }
    Engineer.prototype = new WorkerBee; // 第二层prototype链
    var jay = new Engineer("Jay");
    if (flag)
    {
    alert(jay.dept);    //engineer, 找到的是自己的属性
    alert(jay.hasCar);  // false, 搜索到的是自己上一层的属性
    alert(jay.gender);  // unknown, 搜索到的是自己上二层的属性
    }
    

    上面这个示例的对象关系如下:

    http://farm3.static.flickr.com/2585/3933273719_ccab4562d2.jpg

    结论

    javascript 的prototype给语言本身增加了很强的灵活性,但与 class based programming 相比整个思维逻辑还是有很大的不同,所以需要更多地思考和揣摩.

    而 javascript是披着c语言外衣的函数式语言 的理解自然也需要更多地思考.

    下一节我会继续讨论下 javascript 的另一个重要而且比较容易弄错的知识 closure.

  • 相关阅读:
    五分钟小知识:为什么要分稳定排序和非稳定排序?
    spring boot——配置文件——Spring Boot Profile(多环境配置)——多 Profile 文件方式——properties 配置
    Osquery检测入侵痕迹——这玩意适合在agent端侧使用啊
    2021 fireeye apt攻击报告
    PostgreSQL 使用citd删除重复行 规格严格
    Go Web编程深入学习解析HTTP请求
    网速成为了工作的瓶颈之一
    进销存成本的影响因素
    系统升级时,数据库脚本执行注意事项,血的教训
    进销存系统的成本核算方法一览
  • 原文地址:https://www.cnblogs.com/ahlx/p/5288235.html
Copyright © 2020-2023  润新知