美文网首页
JavaScript创建对象的各种方式

JavaScript创建对象的各种方式

作者: 小泡_08f5 | 来源:发表于2019-06-19 15:57 被阅读0次

    《JavaScript高级程序设计》一共提到了7种创建对象的方式:

    • 工厂模式
    • 构造函数模式
    • 原型模式
    • 构造函数和原型组合模式
    • 动态原型模式
    • 寄生构造模式
    • 稳妥构造模式

    工厂模式

    // 工厂模式
          function Person(){
                var o = new Object(); 
                o.name = 'xiaopao';
                o.say = function(){
                    alert(this.name)
                };
                return o;
            }
    
            var person1 = Person();
            console.log(person1);
            console.log(person1 instanceof Person); // false  无法得知实例的类别
            console.log(person1 instanceof Object); // true
            console.log(person1.__proto__ === Object.prototype); // true
    

    优点: 完成了返回一个对象的要求。
    缺点:

    1. Person ---> Person.prototype ; person1 = o ; o.proto--->Object.prototype
      无法通过constructor识别对象,以为都是来自Object , 无法得知来自Person
    2. 每次通过Person创建对象的时候,所有的say方法都是一样的,却存储了多次,浪费内存资源。

    构造函数模式

    // 构造函数模式
    function Person(name){
                this.name = name;
                this.say = function(){
                    console.log(this.name)
                }
            }
    
            var person1 = new Person('xiaopao');
            console.log(person1);
            person1.say(); // xiaopao
            console.log(person1 instanceof Person); // true  可以的是实例的类别
            console.log(person1.__proto__ === Person.prototype); // true
            
            Person('xiaopao');
            console.log(name); // 添加了一个全局name属性
    

    优点:
    1.可以通过constructor或者instanceof可以识别实例的类型
    2.可以通过new 关键字来创建对象实例,更像OO语言中创建对象实例
    缺点:
    1.多个实例的say方法都是实现一样的效果,但却存储了很多次(存放地址不同)

    构造函数模式优化

    // 构造函数模式优化
            function Person(name){
                this.name = name;
                this.say = getName;
            }
    
            function getName(){ // 这样添加了全局方法,这就没所谓的封装性了
                console.log(this.name);
            }
    
            var person1 = new Person('xiaopao');
            console.log(person1);
            person1.say(); // xiaopao
    

    优点: 解决了每个方法都要被重新创建的问题
    缺点:给全局添加方法,无封装性可言。。

    原型模式

    // 原型模式
    function Person(){};
    
            Person.prototype.name = 'xiaopao';
            Person.prototype.say = function(){
                console.log(this.name);
            }
    
            var person1 = new Person();
            console.log(person1);
            console.log(person1.name); //xiaopao
            person1.say(); // xiaopao
    

    优点:方法不会重新创建
    缺点:
    1.所有属性和方法都共享
    2.不能初始化参数(不能传参)

    原型模式优化

    // 原型模式优化
            function Person(){};
    
            Person.prototype = {
                name: 'xiaopao',
                say: function(){
                    console.log(this.name);
                }
            }
    
            var person1 = new Person();
            console.log(person1.name); // xiaopao
            person1.say(); // xiaopao
            console.log(Person.prototype.constructor); // ƒ Object() { [native code] }   
    

    优点:封装性好一点
    缺点:重写了原型,丢失了constructor属性

    原型模式优化

    // 原型模式优化
            // function Person(){};
    
            // Person.prototype = {
            //     constructor: Person,
            //     name: 'xiaopao',
            //     say: function(){
            //         console.log(this.name);
            //     }
            // }
    
            // var person1 = new Person();
            // console.log(person1.name); // xiaopao
            // person1.say(); // xiaopao
            // console.log(Person.prototype.constructor); // Person(){}
    

    优点:实例可以通过constructor属性找到所属构造函数
    缺点: 原型模式该有的缺点还是有
    原型模式缺点

    // 原型模式缺点  属性是引用类型时
            function Person(){};
    
            Person.prototype.name = 'xiaopao';
            Person.prototype.say = function(){
                console.log(this.name);
            }
            Person.prototype.friends = ['ailse']; // friends属性存储的是内存地址,指向的是同一个数组(引用类型)对象, 当其中一个实例修改数组的值,其他实例访问该属性时值都已改变
    
            var person1 = new Person();
            var person2 = new Person();
            
            console.log(person1.friends,person2.friends); // ["ailse"] ["ailse"]
            person1.friends.push('lulu');
            console.log(person1.friends,person2.friends); // ["ailse", "lulu"]  ["ailse", "lulu"] 
    

    组合模式
    构造函数与原型模式组合

    // 组合模式
            function Person(name){
                this.name = name;
                this.friends = ['ailse'];
            }
    
            Person.prototype = {
                say: function(){
                    console.log(this.name);
                }
            }
    
            var person1 = new Person('xiaopao');
            var person2 = new Person('jianjian');
            console.log(person1.name); // xiaopao
            console.log(person2.name); // jianjian
            person1.say(); // xiaopao
            person2.say(); // jianjian
            person1.friends.push('lulu');
            console.log(person1.friends); // ["ailse", "lulu"]
            console.log(person2.friends); // ["ailse"]
    

    优点:
    1.解决了原型模式对于引用对象的缺点
    2.解决了原型模式没有办法传递参数的缺点
    3.解决了构造函数模式不能共享方法的缺点
    缺点:如果构造函数和原型对象放在一起,封装性强再强一点

    (先new一个对象,再对原型对象通过对面字面量进行赋值的情况)
    直接通过对象字面量给 Person.prototype进行赋值的时候会导致constructor改变,所以需要手动的设置
    直接通过对象字面量给Person.prototype进行赋值,会无法作用在之前创建的对象实例上。

    function Person(name){
                this.name = name;
                this.friends = ['ailse'];
            }
    
    
            var person1 = new Person('xiaopao'); // 报错 Uncaught TypeError: person1.say is not a function
            // Person.prototype.say = function(){
            //     console.log(this.name);
            // } 
            
            Person.prototype.getName = function(){
                console.log(this.name);
            } 
    
            Person.prototype = { // 用对象字面量的形式必须在new实例之前定义,
                constructor: Person, 
                say: function(){
                    console.log(this.name);
                }
            }
    
            var person2 = new Person('jianjian');
            person1.getName(); // xiaopao
            // person1.say(); //  报错
            
            person2.say(); // jianjian
    

    这是因为对象实例和对象原型直接是通过一个指针链接的,这个指针是一个内部属性[[Prototype]], 可以通过proto访问。我们通过对象字面量修改Person.prototype指向的地址,然而对象实例的proto,并没有跟着一起更新,所以这就导致,实例还是访问着原来的Person.prototype, 所以建议不要通过这种方式去改变 Person.prototype属性

    动态原型模式
    JavaScript除了function Person, 还有一个Person.prototype,被定义成了两部分。所以,JavaScript对于封装性还是不够完美,而动态原型模式正是致力于要解决这个问题,它把所有的信息都封装在了构造函数中,通过在构造函数中初始化原型,既很好的体现了封装性,又保持了组合使用构造函数和原型模式的特点,一举两得,非常完美。

    // 动态原型模式  把原型对象属性也放在构造函数当中(封装性强一点),第一次new实例进行原型对象赋值, 之后不重复赋值, 但是不能通过对象字面量进行赋值
            function Person(name, age, job){
                this.name = name;
                this.age = age;
                this.job = job;
                console.log(typeof this.sayName);
                if(typeof this.sayName != 'function'){  
                    Person.prototype.sayName = function(){
                        console.log(this.name);
                    };
            
                    Person.prototype.sayJob = function(){
                        console.log(this.job);
                    };
                }
            }
            
            var p1 = new Person('张三', 18, 'JavaScript');//sayName不存在,添加到原型
            var p2 = new Person('李四', 20, 'Java');//sayName已经存在,不会再向原型添加
            
            p1.sayName();//张三
            p2.sayName();//李四
    

    为什么动态原型模式不能通过对象字面量赋值原型

    image.png
    image.png
    第一次创建实例对象时,先new, 然后执行构造函数,重写原型,那么此时实例的proto指向的还是原来的原型,不是重写后的原型。 第二次创建实例,因为新原型已经创建好了,所以实例的proto指向的就是重写的这个原型。使用给原型添加属性的方式操作的一直是同一个原型,所以也就不存在先后的问题。

    优点:封装性更优秀
    小缺点: 不能使用对象字面量的形式初始化原型

    // 动态原型模式优化
            function Person(name) {
                this.name = name;
                if (typeof this.getName != "function") {
                    Person.prototype = {
                        constructor: Person,
                        getName: function () {
                            console.log(this.name);
                        }
                    }
            
                    return new Person(name);
                }
            }
            
            var person1 = new Person('kevin');
            var person2 = new Person('daisy');
            
            person1.getName(); // kevin
            person2.getName();  // daisy
    

    寄生构造函数模式

    // 寄生构造函数模式
            function Person(name){
                var o = new Object();
                o.name = name;
                o.say = function(){
                    console.log(this.name);
                }
                return o;
            }
    
            var p1 = new Person('xiaopao');
            console.log(p1);
            console.log(p1 instanceof Person); // false
            console.log(p1 instanceof Object); // true
    

    优点: 和工厂模式基本一样,除了多了个new操作符
    缺点: 和工厂模式一样,不能区分实例的类别
    这种方法可以在特殊情况下使用。 比如我们想创建一个具有额外方法的特殊数组,但是又不想直接修改Array构造函数,可以这样写:

    function SpecialArray() {
        var values = new Array();
    
        for (var i = 0, len = arguments.length; i < len; i++) {
            values.push(arguments[i]);
        }
    
        values.toPipedString = function () {
            return this.join("|");
        };
        return values;
    }
    
    var colors = new SpecialArray('red', 'blue', 'green');
    var colors2 = SpecialArray('red2', 'blue2', 'green2');
    
    
    console.log(colors);
    console.log(colors.toPipedString()); // red|blue|green
    
    console.log(colors2);
    console.log(colors2.toPipedString()); // red2|blue2|green2
    

    其实所谓的寄生构造函数模式就是比工厂模式在创建对象的时候,多使用了一个new, 实例上两者的结果是一样的。

    但是作者可能是希望能想使用普通Array一样使用SpecialArray, 虽然把SpecialArray当成函数也一样能用,但是这并不是作者的本意,也变得不优雅。

    在可以使用其他模式的情况下,不要使用这种模式

    稳妥构造函数模式

    // 稳妥构造函数模式
            function Person(name){
                var o = new Object();
                o.name = name;
                o.say = function(){
                    console.log(name);
                }
                return o;
            }
            var p1 = Person('xiaopao');
            console.log(p1.name); // xiaopao
            p1.say(); // xiaopao
            p1.name = 'lulu';
            p1.say();  // xiaopao
            console.log(p1.name) // lulu
    

    优点:安全, name好像成了私有变量,只能通过say方法去访问
    缺点: 不能区分实例的类别

    所谓稳妥对象,指的是没有公共属性,而且其方法也不引用this对象。
    与计生构造函数模式有两点不同:

    1. 新创建的实例方法不引用this
    2. 不使用new操作符调用构造函数
      稳妥对象最适合在一些安全的环境中。

    https://juejin.im/post/5bdf1cf06fb9a049cd53a3a1

    相关文章

      网友评论

          本文标题:JavaScript创建对象的各种方式

          本文链接:https://www.haomeiwen.com/subject/ecyfqctx.html