JavaScript设计模式

目录

普通的创建对象的方式

(1)用字面量形式创建对象

(2)new Object()创建对象

1、工厂模式

2、 函数构造模式

2.1、new操作符

2.2、构造函数的问题

3、原型模式

3.1. in操作符

4、组合模式

 4.1. instanceof运算符

普通的创建对象的方式

(1)用字面量形式创建对象

var person = {
    name: "zhangsan",
    age: 18,
    sayName: function(){
        console.log(this.name);
    }
}

(2)new Object()创建对象

//先实例化一个对象
var person = new Object();
//为这个实例化的对象添加属性
person.name = "zhangsan";
person.age = 18;
person.sayName = function(){
    console.log(this.name)
}

上述两种方法中,字面量创建的对象是一次性的,创建多少个对象就要写多少次,非常繁琐。第二种方式同样是一次只能创建一个对象,且它是先实例化了一个对象,然后再为对象添加属性,整体性弱。

1、工厂模式

  优点:可以批量创建对象 提高代码复用率

  缺点:方法冗余,无法区分实例对象的种类,创建的实例都是Object的实例

//将创建对象的代码封装在一个函数中
function createPerson(name, age, gender) {
    var person = new Object();
    person.name = name;
    person.age = age;
    person.gender = gender;
    person.sayName = function () {
      console.log(this.name);//这里每创建一个对象就会在堆内占据一个内存,造成内存浪费
    }
    return person;
  }
  //利用工厂函数来创建对象
  var person1 = createPerson("zhangsan", 18, '男');
  var person2 = createPerson("lisi", 20, '男');
  console.log(p1.sayName === p2.sayName);//false  

var getName=function(){
    console.log(this.name);
};//将方法写到函数外面可以避免内存浪费,但是所用实例都可以调用这个方法
function Person(name,age,gender){
    return {
        name:name,
        age:age,
        gender:gender,
        getName:getName
    }
}
var p1=Person('zhangsan',12,'男');
var p2=Person('lisi',13,'男');
console.log(p1,p2);//{ name: 'terry', age: 12, gender: '男', getName: [Function: getName] } { name: 'larry', age: 13, gender: '男', getName: [Function: getName] }无法区分种类,创建的实例都是Object实例
console.log(p1.getName===p2.getName);//true 调用的同一个方法,堆内存地址相同
console.log(p1 instanceof Person);//false无法区分种类
console.log(p1 instanceof Object);//true 创建的实例都是对象实例

2、 函数构造模式

优点:可以批量创建对象,可以区分种类

缺点:方法冗余

var getName=function(){
    console.log(this.name);
}
function Person(name,age,gender){
    this.name=name;
    this.age=age;
    this.gender=gender;
    this.getName=getName;
}
var p1=new Person('zhangsan',18,'男');
var p2=new Person('lisi',19,'男');
console.log(p1,p2);// Person {name:'zhangsan',age:18,gender:'男',getName: [Function: getName]} 可以区分实例对象的种类
console.log(p1.getName===p2.getName);// true

 构造函数模式相较于工厂模式可以确保实例被标识为特定的类型,可以确定其种类(比如上面的案例就是Person对象的实例)。

注意:

(1)在实例化时,如果不想传参数,构造函数后面的括号可加可不加。只要有 new 操作符,就可以调用相应的构造函数。

var person1 = new Person();
var person2 = new Person;

(2)构造函数也是函数,没有将某个函数定义为特殊函数的语法。任何函数只要使用new操作符调用就是构造函数,不使用new操作符调用的就是普通函数。这时候调用Person()结果会将属性和方法添加到全局对象。

function Person(name, age) {
    this.name = name;
    this.age = age;
    this.getName = function () {
      console.log(this.name);
    };
}
// 作为构造函数
var person = new Person("zhangsan", 18);
person.getName(); // zhangsan
// 作为函数调用 且没有明确设置this指向的情况下(即没有作为对象的方法调用或没使用call/bind等改变this指向)this始终指向global全局对象(浏览器中指向window对象)
Person("lisi", 18); // 属性和方法会被添加到全局对象
global.getName(); // lisi

2.1、new操作符

new操作符在new一个对象的过程中做了哪些事情?

1、创建一个新的实例对象

2、这个新对象内部[[[Prototype]]特性指向构造函数的原型对象属性

3、this指向新对象

4、执行构造函数函数体,给新对象添加属性

5、返回创建的新对象

2.2、构造函数的问题

构造函数时,其定义的方法会在每个实例上都创建一遍,如上面的案例,创建了p1、p2两个实例,它们都有一个getName()方法,但是不同实例上的函数虽然同名却不相等。

function Person(name){
    this.name = name;
    this.getName = function() {
        console.log(this.name);
    };
}
var p1=new Person('zhangsan');
var p2=new Person('lisi');
console.log(p1.getName===p2.getName);//false

//解决方法  将定义的方法转移到构造函数外部
var getName=function(){
    console.log(this.name);
}
function Person(name){
this.name = name;
}
var p1=new Person('zhangsan');
var p2=new Person('lisi');
console.log(p1.getName===p2.getName);//true

这个方法虽然解决了这个问题,但是此时的getName()方法已成为了全局方法,任何对象都可以调用这个方法,全局的作用域会因此混乱,且如果要定义多个函数,这会导致自定义类型引用的代码不能聚集在一起,致使代码结构混乱。这个新的问题可通过原型模式解决。

3、原型模式

原型模式定义的属性和方法是由所有实例共享的。因此 p1 和 p2 访问的都是相同的属性和相同的 getName()函数。

// 将所有的属性和方法全部维护到原型对象中 构造函数中不存放任何属性和方法
// 不单独使用
function Person(){};
Person.prototype={
    //设置constructor的值
    constructor:Person,// 因为Person.prototype被作为通过字面量形式创建的新对象,完全重写了默认的prototype对象,constructor属性不在指向Person,而是指向新对象(Object构造函数)。解决这个问题,可以在重写原型对象时,设置constructor的值。
    name:'terry',
    age:12,
    friends:[],
    getName:function(){
        console.log(this.name);
    }
}
//创建两个新对象
var p1=new Person();
var p2=new Person();
console.log(p1,p2);//Person {} Person {}
console.log(p1.getName===p2.getName);//true
p1.friends.push('larry');
console.log(p1.friends,p2.friends);//[ 'larry' ] [ 'larry' ]
console.log(p1.name);//terry
//在实例上添加一个name属性
p1.name = 'zhangsan';
console.log(p1.name); //zhangsan原型对象上的name属性被实例对象的name属性遮住了
console.log(p2.name);//teery

在使用案例中的方式设置constructor的值时,会创建一个[[Enumerable]]为true(表示可枚举)的属性。而原生constructor是不可枚举的。可以改用Object.defineProperty()方法恢复constructor属性。

function Person(){};
Person.prototype={
    name:'terry',
    age:12,
    friends:[],
    getName:function(){
        console.log(this.name);
    }
}
Object.defineProperty(Person.prototype, "constructor", {
    enumerable: false,
    value: Person
  });

由上面的案例可知,虽然可以通过实例读取原型对象上的值,但不可能通过实例重写这些值。实例上添加了一个与原型对象中同名的属性,那就会在实例上创建这个属性,这个属性会遮住原型对象上的属性。

验证:我们可以通过hasOwnProperty()验证p1.name访问的是实例属性还是原型属性。实例属性返回true

console.log(p1.name);//terry
console.log(p1.hasOwnProperty('name'));//false
//添加实例属性
p1.name = 'zhangsan';
console.log(p1.name); //zhangsan
console.log(p1.hasOwnProperty('name'));//true 此时,表明name是一个实例属性,不是原型属性

 给对象添加实例属性后,这个属性会遮蔽原型对象上与之同名的属性,只有使用delete操作符才可以完全删除实例上的属性,恢复对象和原型对象上这个属性的联系。

console.log(p1.name);//terry
console.log(p1.hasOwnProperty('name'));//false
//添加实例属性
p1.name = 'zhangsan';
console.log(p1.name); //zhangsan
console.log(p1.hasOwnProperty('name'));//true
//删除实例属性
delete p1.name;
console.log(p1.name);//terry
console.log(p1.hasOwnProperty('name'));//false

图解:

 

3.1. in操作符

作用:检查一个属性是否在该对象实例上还是在原型上,无论是在实例上还是原型上,都可以检测到

语法:属性名 in 实例对象名   返回值:true/false

console.log('name' in p1); // true

原型模式将所有方法全部维护到原型对象中有好处,但任然存在一些弊端,比如上面的案例中的friends属性,所有实例对象的这个属性都会是同一个值,但实际上不同的实例对象应该有属于自己的私有属性。这也是为什么不单独使用原型模式的原因。

4、组合模式

组合模式:将构造模式和原型模式组合使用

// 将实例的私有的属性和私有方法维护到构造函数中
// 将实例的公共属性和公共方法维护到原型对象中
function Person(name,age,gender){
    this.name=name;
    this.age=age;
    this.gender=gender;
    this.friends=[];
}
Person.prototype={
    constructor:Person,
    getName(){
        console.log(this.name);
    }
}
var p1=new Person("terry",12,'男');
var p2=new Person("larry",13,'男');
console.log(p1,p2);// 
p1.getName();
p2.getName();
console.log(p1.getName===p2.getName);// 
console.log(p1 instanceof Person);// 
console.log(p1 instanceof Object);// 
p1.friends.push("tom");
console.log(p1,p2);// 
var arr=new Array();//
console.log(arr instanceof Object);

 4.1. instanceof运算符

用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。或者说判断一个对象是某个对象的实例。

运用:前面案例中每个案例既是Object的实例也是Person的实例,下面调用instanceof确定实例p1的对象类型。

console.log(p1 instanceof Person);// true 表示p1的对象类型为Person
console.log(p1 instanceof Object);// true 结果为true表示p1的对象类型是Object
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值