Javascript对象创建的几种方式

本文是Nicholas的《Javascript高级程序设计》6.2章的读书笔记,仅在加强记忆


  1. 工厂模式。即用一个函数来生成类的实例
    function creator(name, age, job){
        var o = {};
        o.name = name;
        o.age = age;
        o.job = job;
        o.getName = function(){
            return this.name;
        };
        return o;
    }
    
    var person1 = creator('siglud', 10, 'Engineer');
    var person2 = creator('ethlin', 12, 'Doctor');
    
    console.log(person1.getName == person2.getName);
    
    这种方法的很好理解,就是构造一个能返回一个类实例的函数,但是弊端有两个,一是无法用反射知道一个对象的类型,因为这个对象的类型是Object(例子中我用{}来代替);二是getName作为一个函数本来是可以复用的,但是却其实生成了两个,所以在最后的console log中打印出了false
  2. 生成器模式。原版翻译为构造函数模式,但是这个“构造函数”明显是和C语言中的构造函数不是一个东西,所以我换个说法,其本质就是把函数本身作为一个类,用new关键字来生成一个新的函数(类)对象,因为JS中函数本身也是一个对象
    function Person(name, age, job){
        this.name = name;
        this.age = age;
        this.job = job;
        this.getName = function(){
            return this.name;
        }
    }
    
    var person3 = new Person('siglud', 10, 'Engineer');
    var person4 = new Person('ethlin', 12, 'Doctor');
    
    console.log(person3 instanceof Person);
    console.log(person4 instanceof Person);
    console.log(person3.getName == person4.getName)
    
    这种的算是正统的构造对象的方法了,但是问题就是getName是同一个函数,但是在不同的对象中却是不同的,不是一个可以复用的东西,当然,你可以用一个全局的函数来替代它,但是很明显,这不是一个好主意。
  3. 原型模式。不在生成器函数中写入任何属性,而是放到prototype中,让new出来的对象直接继承prototype的属性
    function Person2(){}
    
    Person2.prototype.name = "default name";
    Person2.prototype.age = 18;
    Person2.prototype.job = 'Doctor';
    Person2.prototype.getName = function(){
        return this.name;
    };
    
    var person5 = new Person2();
    console.log(person5.getName());
    
    var person6 = new Person2();
    person6.name = 'Siglud';
    person6.age = 19;
    person6.job = 'Engineer';
    console.log(person6.getName());
    
    console.log(person5 instanceof Person2);
    console.log(person6 instanceof Person2);
    console.log(person5.getName == person6.getName);
    
    这种方法就是经常用的最多的原型模式了,函数也是公用的原型函数,但是注意到person6的name是person6自己的属性,而person5用的是它的prototype的属性,会被hasOwnProperty判定为false的,person6的prototype其实也有name属性,但是当它也有了自己的name属性的时候,它就不再向prototype请求和使用这个属性了。这里需要注意的是我用的是
    Person2.prototype.name = "default name";
    Person2.prototype.age = 18;
    Person2.prototype.job = 'Doctor';
    
    这样给原有的原型对象赋值的方式,你也可以用以下方法把原型对象完全替换掉:
    Person2.prototype = {
        name: 'default name',
        age: 18,
        job: 'Doctor',
        getName: function(){
            return this.name;
        }
    };
    
    这样的问题一个就是person5的constructor不再指向Person2了,当然,你也可以这样修正:
    Person2.prototype = {
        constructor: Person2,
        name: 'default name',
        age: 18,
        job: 'Doctor',
        getName: function(){
            return this.name;
        }
    };
    
    但是这样又会产生一个问题,如果这段代码写在var person5 = new Person2();之前是没问题的,如果写在之后,person5将不会具有新赋值的Person2.prototype的全部属性,因为它两个已经指向两个完全不同的目标。除此之外,原型继承还是有坑的,体现在类似Python的函数参数默认值为可变量(如list)时一样的坑:
    function Person3(){}
    
    Person3.prototype.name = "default name";
    Person3.prototype.age = 18;
    Person3.prototype.job = 'Doctor';
    Person3.prototype.friends = ['default friends'];
    Person3.prototype.getName = function(){
        return this.name;
    };
    
    var person7 = new Person3();
    var person8 = new Person3();
    
    person7.friends.push('jack');
    
    console.log(person8.friends);
    
    这时候输出的居然是["default friends", "jack"],当然你也可以这样改造来让它正常
    function Person3(){}
    
    Person3.prototype.name = "default name";
    Person3.prototype.age = 18;
    Person3.prototype.job = 'Doctor';
    Person3.prototype.friends = ['default friends'];
    Person3.prototype.getName = function(){
        return this.name;
    };
    
    var person7 = new Person3();
    var person8 = new Person3();
    
    /*person7.friends.push('jack');
    
    console.log(person8.friends);*/
    
    person7.friends = [];
    person7.friends.push('jack');
    
    console.log(person8.friends);
    console.log(person7.friends);
    
    但是我们没办法约束客户端程序员每个都这么乖乖的去写,所以这也是独立的使用原型模式比较少的原因。
  4. 组合模式 其实就是综合使用原型模式和构造函数模式
    function ComplexPerson(name, age, job){
        this.name = name;
        this.aget = age;
        this.job = job;
    }
    
    ComplexPerson.prototype = {
        constructor: ComplexPerson,
        sayName : function(){
            return this.name;
        }
    };
    
    var person_complex_1 = new ComplexPerson('siglud', 11, 'Worker');
    var person_complex_2 = new ComplexPerson('ethlin', 12, 'Doctor);
    
    console.log(person_complex_1 instanceof ComplexPerson);
    console.log(person_complex_2 instanceof ComplexPerson);
    
    console.log(person_complex_1.sayName());
    console.log(person_complex_2.sayName());
    
    这样又做到了函数的复用,又做到了类型统一
  5. 动态原型模式

    这个其实和上面的没啥区别,唯一的区别在于它不会重复的定义函数,它通过首先检查对应的方法是否存在然后再决定是否初始化它

    function ComplexPerson(name, age, job){
        this.name = name;
        this.aget = age;
        this.job = job;
    }
    
    if(typeof ComplexPerson.sayName !== 'function'){
        ComplexPerson.prototype = {
            constructor: ComplexPerson,
            sayName : function(){
                return this.name;
            }
        };
    }
    
  6. 寄生构造函数模式

    寄生构造函数其实就是工厂模式,但是Nicolas把它独立出来是因为这货虽然并不怎么好用,但是偶尔却能处理一些很麻烦的问题,比如你想继承系统的Array,并且给它添加一部分功能,当然,你不能随意的去给系统的array的prototype添加功能,这样会给其他人埋坑,所以这个时候工厂模式就有了用武之地了,举例就不用了,其实是工厂模式

  7. 稳妥构造函数模式

    这也是一个为了特殊目的而构造的东西,它的最大目的是隐藏内部变量,不允许直接使用this来访问内部属性,相当于一个非常极端的把属性设置为private的方式,因为js基本上没办法让类的属性变成private

    function DurablePerson(name, age, job){
        var o = {};
        
        o.sayMyName = function(){
            return name;
        };
        return o;
    }
    
    var durablePerson = DurablePerson('siglud', 11);
    
    console.log(durablePerson.sayMyName());
    console.log(durablePerson.name);
    

    可以看到第二个试图访问的结果被抛出错误了,当然,你也可以给他定义setter,让它可以更改,但是除了你开放的接口,你别人终于无法改动你的变量了

    function DurablePerson(name, age, job){
        var o = {};
    
        o.sayMyName = function(){
            return name;
        };
    
        o.setMyName = function(myName){
            name = myName;
        };
    
        return o;
    }
    
    var durablePerson = DurablePerson('siglud', 11, 'Worker');
    
    console.log(durablePerson.sayMyName());
    durablePerson.setMyName('Ethlin');
    console.log(durablePerson.sayMyName());
    

评论

此博客中的热门博文

转一下关于Fuck的用法

远程记录OpenWRT日志

用OpenWRT打造自动翻墙路由器(详解篇)