视频1 视频21 视频41 视频61 视频文章1 视频文章21 视频文章41 视频文章61 推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37 推荐39 推荐41 推荐43 推荐45 推荐47 推荐49 关键词1 关键词101 关键词201 关键词301 关键词401 关键词501 关键词601 关键词701 关键词801 关键词901 关键词1001 关键词1101 关键词1201 关键词1301 关键词1401 关键词1501 关键词1601 关键词1701 关键词1801 关键词1901 视频扩展1 视频扩展6 视频扩展11 视频扩展16 文章1 文章201 文章401 文章601 文章801 文章1001 资讯1 资讯501 资讯1001 资讯1501 标签1 标签501 标签1001 关键词1 关键词501 关键词1001 关键词1501 专题2001
JavaScript面向对象-基于组合和动态原型创建对象
2020-11-27 20:26:18 责编:小采
文档
前面两篇文章我们介绍了JavaScript中原型的内存模型和原型的重写方法即注意事项。在了解原型之后,我们就可以通过原型来创建JavaScript对象。基于原型的创建方式虽然可以有效的完成封装,但是依然会存在一些问题。

通过原型的方式来创建对象主要会产生2个问题:

  • 1、无法通过构造函数来设置对象的属性值。

  • 2、当属性中有引用类型变量时,可能会存在变量值的重复。

  • 我们来看下面的例子:

    function Person(){}
    Person.prototype = {
     constructor:Person,
     name:"Leon",
     age:22,
     friends:["Ada","Chris"],
     say:function(){
     console.info(this.name+"["+this.friends+"]");
     }
    }

    在上面的代码中,我们创建了一个Person类,并通过原型重写的方式为它设置了一些属性和方法,其中有一个friends属性,是一个引用类型的数组。接下来我们通过Person类来创建对象,代码如下:

    var p1 = new Person();
    p1.name = "John";
    p1.say(); //控制台
    输出:Jhon[Ada,Chris]

    我们通过Person类创建了对象p1,可以看到使用原型方式创建对象时没有办法为对象p1设置属性,我们只有在对象创建之后才设置p1的name属性。接着调用p1的say()方法,控制台会输出Jhon[Ada,Chris],到这里一切都还是正常的。

    如果我们接着为对象p1添加一个新的朋友,问题就会出现了。代码如下:

    p1.friends.push("Mike"); //为p1增加一个朋友(注意这里是在原型中添加)
    p1.say();

    我们通过数组的push方法为p1添加一个新的朋友“Mike”,此时,在对象p1自己的空间中是没有friends属性的,所以“Mike”会被添加到Person的原型中,如下图所示:

    由于新加入的数组元素是放置在原型中的,所以后面创建的所有对象都会共享这个属性,这时我们创建对象p2的话,他的朋友中也会有一个“Mike”。这是我们不希望看到的结果。

    var p2 = new Person();
    //如果p1 push之后,原型中就多了一个人,p2也多了一个朋友
    p2.say();

    基于组合原型和构造函数的方式创建对象

    为了解决上面的问题,我们可以使用基于组合原型和构造函数的方式来创建对象。也就是将属性在构造函数中定义,将方法在原型中定义。这种方式有效的集合了两者的优点,是我们在JavaScript中最常用的一种创建对象的方式。

    function Person(name,age,friends){
     //属性在构造函数中定义
     this.name = name;
     this.age = age;
     this.friends = friends;
    }
    Person.prototype = {
     //方法在原型中定义
     constructor:Person,
     say:function(){
     console.info(this.name+"["+this.friends+"]");
     }
    }

    通过这种方式创建的对象,所有的属性都是保存在对象自己的空间中的。此时,在创建对象的时候,我们就可以为对象设置它自己的属性。

    var p1 = new Person("Leon",22,["Ada","Chris"]);
    p1.name = "John";
    p1.say(); //控制台
    输出: John[Ada,Chris]

    完成上面的代码后,Person类及p1对象的内存模型如下图所示:

    此时,我们再为对象p1添加一个新的朋友时,会在p1对象自己的内存空间中的friends属性中添加。这样,每个对象的属性都是的,不会互相干扰。

    p1.friends.push("Mike"); //为p1增加一个朋友(注意这里是在p1自己的空间中添加)
    p1.say(); //控制台
    输出: John[Ada,Chris,Mike] var p2 = new Person(); p2.say(); //控制台输出: John[Ada,Chris]

    因此,现在再创建对象p2时,p2对象的friends只会是“Ada”和“Chris”,而没有“Mike”。

    动态原型方式创建对象

    虽然基于组合原型和构造函数的方式创建对象已经非常完美了,但是它和纯正的面向对象语言创建对象的方式还是有一些差别:类的方法被定义在类之外。为了让定义对象更加符合面向对象规范的需求,我们可以把定义方法的原型代码放置到Person构造函数中。这种方式我们称为动态原型方式创建对象。

    // 动态原型方式
    function Person(name,age,friends){
     this.name = name;
     this.age = age;
     this.friends = friends;
     
     Person.prototype.say = function(){
     console.info(this.name+"["+this.friends+"]");
     }
    }

    注意在使用动态原型方式创建对象的时候,我们在定义方法的时候不能够使用原型重写的方式,例如下面的代码是错误的:

    // 错误的动态原型方式
    function Person(name,age,friends){
     this.name = name;
     this.age = age;
     this.friends = friends;
     
     //不能使用原型重写的方式来设置方法
     Person.prototype = {
     constructor:Person,
     say:function(){
     console.info(this.name+"["+this.friends+"]");
     }
     }
    }

    使用动态原型方式创建对象同样会存在问题,因为类中的方法是通过Person.prototype.say的方式创建的,这样每次创建对象的时候,都会在内存中创建一个新的say()方法。解决这个问题的方法是我们可以先做一个判断,看Person.prototype.say方法是否存在,不存在时才创建,否则就不创建。

    // 动态原型方式
    function Person(name,age,friends){
     this.name = name;
     this.age = age;
     this.friends = friends;
     
     //判断Person.prototype.say是否存在,不存在就创建
     if(!Person.prototype.say){
     alert("创建say方法");
     Person.prototype.say = function(){
     console.info(this.name+"["+this.friends+"]");
     }
     }
    }

    为了验证判断条件是否起作用,我们在代码中的判断分支中添加了一个弹出对话框语句。我们可以创建2个对象,然后2个对象分别调用say()方法,在结果中,第一个对象在调用say()方法时会弹出对话框,而第二个对象在调用say()方法时就不会在弹出对话框了,也就是说创建第二个对象时不会再添加say()方法。

    下载本文
    显示全文
    专题