zoukankan      html  css  js  c++  java
  • JS对象进阶创建对象的5种模式

    JS对象类型-对象-认识对象一文中,介绍了创建对象的3种方式:new构造函数、对象字面量和Object.create()函数。下面基于这3种方式介绍下创建对象的5种常用模式。

    对象字面量

    var p1 = {
      name: 'li',
      age: 10,
      sex: 'boy'
    }
    
    var p2 = {
      name: 'wang',
      age: 20,
      sex: 'girl'
    }
    

    对象字面量方式虽然可以创建单个对象,但是如果同时创建多个类似对象,会产生大量冗余代码。

    工厂模式

    为了解决创建多个对象造成代码冗余的问题,人们发明了工厂模式。该模式通过函数来封装特定的接口。

    function People(name,age,sex) {
      var o = new Object();
      o.name = name;
      o.age = age;
      o.sex = sex;
      o.sayName = function(){
        console.log(this.name);
      }
      return o;
    }
    
    var p1=People('li', 10, 'boy');
    var p2=People('wang', 20, 'girl');
    
    console.log(p1 instanceof People) // false
    

    使用这种方式虽然解决了创建多个对象的问题,但是却没办法识实例对象的类型。

    构造函数模式

    通过创建自定义构造函数来自定义对象的属性和方法,这种方式可以把实例标识为一种特定类型,这也是构造函数胜过工厂模式的原因。

    function People(name, age, sex) {
      this.name=name;
      this.age=age;
      this.sex=sex;
      this.sayName=function() {
        console.log(this.name);
      }
    }
    
    var p1=new People('li', 10, 'boy');
    var p2=new People('wang', 20, 'girl');
    
    console.log(p1 instanceof People); // true
    
    

    看起来似乎已经完美了,其实使用构造函数模式也有一些问题。它会把每个方法在实例上重新创建一遍,创建多个完成同样任务的函数是没有必要的。

    // 两个实例的sayName方法占用不同的内存空间
    console.log(p1.sayName===p2.sayName); //false
    

    构造函数拓展模式

    为了解决构造函数重复创建相同方法的问题,在构造函数的基础上引入拓展模式,即把方法移到构造函数外部。

    function People(name, age, sex) {
      this.name=name;
      this.age=age;
      this.sex=sex;
    }
    
    function sayName{
      console.log(this.name);
    }
    
    var p1=new People('li', 10, 'boy');
    var p2=new People('wang', 20, 'girl');
    
    console.log(p1.sayName===p2.sayName); //true
    

    使用这种方式又有了新问题,把方法定义到全局作用域中显然污染了全局空间,如果方法有很多肯定会乱成一锅粥,更没有封装性可言了。

    寄生构造函数模式

    寄生构造函数模式是工厂模式和构造函数的结合,它兼具了这两种模式的缺点,没错是缺点。不仅每个方法都要在每个实例上重新创建一遍,而且没办法识实例对象的类型。所以尽量不要使用这种模式。

    function People(name, age, sex) {
      var o=new Object();
      o.name=name;
      o.age=age;
      o.sex=sex;
      o.sayName=function() {
        console.log(this.name);
      }
      return o;
    }
    
    var p1=new People('li', 10, 'boy');
    var p2=new People('wang', 20, 'girl');
    
    console.log(p1.sayName === p2.sayName) // false
    console.log(p1 instanceof People) // false
    

    稳妥构造函数模式

    稳妥对象没有公共属性,并且方法不能引用this对象。稳妥模式可以防止数据内其他应用程序修改,所以适合在一些安全环境中使用。

    function People(name, age, sex) {
      var o=new Object();
      o.sayName=function() {
        console.log(name);
      }
      return o;
    }
    
    var p1=People('li', 10, 'boy');
    var p2=People('wang', 20, 'girl');
    
    console.log(p1.sayName===p2.sayName) // false
    console.log(p1 instanceof People) // false
    

    和寄生模式类似,不仅每个方法都要在每个实例上重新创建一遍,而且没办法识实例对象的类型。

    原型模式

    通过原型对象,让所有实例共享它的属性和方法。

    function People(name, age, sex) {
      People.prototype.name = name;
      People.prototype.age = age;
      People.prototype.sex = sex;
      People.prototype.sayName = function(){
        console.log(this.name)
      }
    }
    
    var p1=new People('li', 10, 'boy')
    var p2=new People('wang', 20, 'girl')
    
    console.log(p1.sayName===p2.sayName) // true
    console.log(p1 instanceof People) // true
    

    【更简单简洁的原型模式】

    function People() {}
    People.prototype = {
      name: 'li',
      age: 10,
      sex: 'boy',
      sayName: function(){
        console.log(this.name)
      }
    };
    
    var p1 = new People();
    p1.sayName(); // 'li'
    

    缺点: 这种简洁的方式没办法初始化参数。
    注意: 使用包含所有属性和方法的对象字面量重写原型对象后,constructor属性不再指向People构造函数。因为重写后没有显式的定义constructor属性,它就只能从原型链上Object.prototype中获取constructor属性。

    p1.constructor === People // false
    p1.constructor === Object // true
    

    解决方法是显式的设置constructor属性。

    function People() {}
    
    People.prototype= {
      constructor: People,
      name: 'li',
      age: 10,
      sex: 'boy',
      sayName: function() {
        console.log(this.name)
      }
    }
    var p1 = new People();
    p1.constructor===People; // true
    

    另外原生的constructor属性是不可枚举的,所以为了保持一致,最好使用Object.defineProperty()方法把constructor属性设置成不可枚举的。

    function People() {}
    
    People.prototype= {
      constructor: People,
      name: 'li',
      age: 10,
      sex: 'boy',
      sayName: function() {
        console.log(this.name)
      }
    }
    Object.defineProperty(People.prototype, 'constructor', {
      enumerable: false,
      value: People
    })
    var p1=new People();
    p1.constructor===People; // true
    

    使用原型模式也不是最完美的,它的问题是引用类型值的属性可以被所有实例共享和修改。

    function People() {};
    People.prototype.hobbies=['阅读','编程','跑步'];
    
    var p1=new People()
    var p2=new People()
    
    p1.hobbies.push('音乐')
    console.log(p2.hobbies); // ["阅读", "编程", "跑步", "音乐"]
    

    组合模式

    组合模式是构造函数模式和原型模式的结合,是创建自定义类型最常用的方式。构造函数模式用于定义实例的属性,原型模式用于定义方法和共享的属性。每个实例都有自己的属性副本,并且共享对方法的引用。组合模式是目前使用最广泛的创建自定义对象的模式。

    function People(name,age,sex,hobbies) {
      this.name = name
      this.age = age
      this.sex = sex
      this.hobbies = hobbies
    }
    People.prototype = {
      constructor: People,
      school: 'Tsinghua',
      sayName: function() {
       console.log(this.name)
      }
    }
    
    var p1=new People('li', 10, 'boy', ['音乐'])
    var p2=new People('wang', 20, 'girl',['编程'])
    
    p1.hobbies.push('跑步')
    console.log(p2.hobbies) // ['编程']
    
    p1.__proto__.school = 'Hafer'
    console.log(p2.school) // 'Hafer'
    
    console.log(p1.sayName === p2.sayName) // true
    console.log(p1 instanceof People) // true
    
    

    动态原型模式

    动态原型模式把组合模式中的构造函数和原型对象都封装到了构造函数中,通过检查方法是否被创建来决定是否初始化原型对象。

    function People(name,age,sex,hobbies) {
      this.name = name
      this.age = age
      this.sex = sex
      this.hobbies = hobbies
      if(typeof this.sayName != 'function') {
        console.log(typeof this.name)
         People.prototype.sayName = function() {
           console.log(this.name)
         }
      }
    }
    
    var p1=new People('li', 10, 'boy', ['音乐'])
    
    console.log(p1.sayName())
    

    总结

    【字面量方式】
    缺点:多个对象造成代码冗余

    【工厂模式】
    缺点:无法识别实例类型

    【构造函数模式】
    缺点:每个实例都会创建一个相同的方法,造成内存浪费

    【原型模式】
    缺点:引用类型值的属性会被所有实例共享和修改

    【组合模式】
    目前使用最广泛的一种模式,解决上了以上模式的所有缺点。

    优秀文章首发于聚享小站,欢迎关注!
  • 相关阅读:
    【ClickHouse 技术系列】 ClickHouse 聚合函数和聚合状态
    【ClickHouse 技术系列】 ClickHouse 中的嵌套数据结构
    报表功能升级|新增的这4项图表组件太太太好用了吧!
    【视频特辑】数据分析师必备!快速制作一张强大好用的大宽表
    使用云效Codeup10分钟紧急修复Apache Log4j2漏洞
    技术干货 | 使用 mPaaS 配置 SM2 国密加密指南
    “全”事件触发:阿里云函数计算与事件总线产品完成全面深度集成
    3类代码安全风险如何避免?
    为余势负天工背,云原生内存数据库Tair助力用户体验优化
    LeetCode_Search for a Range
  • 原文地址:https://www.cnblogs.com/yesyes/p/15352081.html
Copyright © 2011-2022 走看看