JS创建对象方法比较

本文探讨了六种在JavaScript中创建对象的方法,包括工厂模式、构造函数模式、原型模式、组合构造函数和原型模式、动态原型模式及寄生构造函数模式。详细分析了每种方法的优缺点,并给出具体代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

//JS创建同一类对象方法比较
//使用构造函数或对象字面量都可以创建单个对象,但是创建多个相似对象就会产生大量重复代码,可以使用以下方式
//来创建同一类对象:

//1.工厂模式:用函数来封装以特定接口创建对象的细节
function createPerson(name, age) {
    let o  = new Object();
    o.name = name;
    o.age = age;
    o.sayName = function () {
        console.log(this.name);
    };
    return o;
}
let p1 = createPerson('Bob', 23);
let p2 = createPerson('John', 28);

console.log(typeof p1);  // object
console.log(p1 instanceof Object); // true

//工厂模式解决了创建同类对象不用产生大量重复代码的问题,但是无法知道对象的具体类型,只能知道是Object类型


//2.构造函数模式
//a.
function Person(name, age) {
    this.name = name;
    this.age = age;
    this.sayName = function () {
        console.log(this.name);
    };
}

p1 = new Person('Bob', 23); //将构造函数的作用域赋给新对象,构造函数里的this就指向调用该方法的新对象
p2 = new Person('John', 28);

console.log(p1);  // object
console.log(p1 instanceof Object); // true
console.log(p1 instanceof Person); // true (既是Object的实例也是Person的实例)

console.log(p1.sayName === p2.sayName);  //false

//构造函数模式可以用来标志对象的具体类型,但其定义的方法要在每个实例上创建一遍,无法做到函数复用

//b.构造函数模式2
function Person2(name, age) {
    this.name = name;
    this.age = age;
    this.sayName = sayName;
}
function sayName() {
    console.log(this.name);
}

p1 = new Person2('Bob', 23);
p2 = new Person2('John', 28);
console.log(p1.sayName === p2.sayName);  //true

//把方法移到构造函数外,实例就共享了全局作用域定义的同一个方法,但是如果需要多个方法,就需要定义多个全局
// 函数,毫无封装性可言

//3.原型模式
//a.
function Person3() {
}
Person3.prototype.name = 'Ann';
Person3.prototype.age = 25;
Person3.prototype.sayName = function () {
    console.log(this.name);
};
Person3.prototype.friends = ['a','b'];

p1 = new Person3();
p2 = new Person3();

console.log(p1.name);  //Ann
p1.name = 'Bob';
console.log(p1.name);  //Bob
console.log(p2.name);  //Ann (p1定义的属性覆盖了原型属性,成为p1自己的属性,无法影响到其他实例)
console.log(p1.sayName === p2.sayName);  //true
console.log(p1 instanceof Person3);  //true
console.log(p1.constructor);  //Person3

p1.friends.push('c');
console.log(p1.friends);  //[ 'a', 'b', 'c' ]
console.log(p2.friends);  //[ 'a', 'b', 'c' ]  (p2的friends属性值也修改了)

//原型模式可以保证良好的封装性,在其上面定义的属性和方法都是实例共享的,且实例可以自定义属性覆盖原型里的
// 同名属性成为自己的属性,但是对于包含引用类型的属性(friends)来说,无法做到这一点,修改引用类型的属性
// 值,会影响所有实例的该属性,因为所有实例共有的引用类型属性的地址是同一个地址

//可以直接修改引用属性值的引用地址:
p1.friends = ['d', 'e'];
console.log(p1.friends);  //[ 'd', 'e' ]
console.log(p2.friends);  //[ 'a', 'b', 'c' ]  //(p2的friends属性值未修改)

//该原型模式写法每添加一个属性或方法都要写一遍Person.prototype,较为繁琐,封装性难以体现

//b.原型模式的简单写法:使用对象字面量来重写整个原型对象,更好体现封装性
function Person4() {
}
Person4.prototype = {
  name: 'Ann',
  age: 25,
  sayName: function () {
    console.log(this.name);
  }
};

p1 = new Person4();

console.log(p1 instanceof Person4);  //true
console.log(p1.constructor);  //Object (无法指向原构造函数Person)

//原型模式简单写法产生的问题1:由于对象的constructor属性并非指向其构造函数,而是指向其构造函数的原型对象
// 的constructor属性,此处构造函数的原型对象指向Object,其constructor属性指向Object

//可以在重写原型时显示添加constructor属性指向原来的构造函数
Person4.prototype = {
  constructor: Person4,
  name: 'Ann',
  age: 25,
  sayName: function () {
    console.log(this.name);
  }
};
p1 = new Person4();
console.log(p1.constructor);  //Person4

//原型模式简单写法产生的问题2:如果先创建实例,再重新定义原型:
Person4.prototype = {
  constructor: Person4,
  name: 'Ann',
  age: 25,
  sayName: function () {
    console.log(this.name);
  },
  friends: ['a']
};
console.log(p1.__proto__); //{constructor: [Function: Person4],name: 'Ann',age: 25,
             // sayName: [Function: sayName] }  (没有新定义原型里的friends属性)

//把原型修改为另一个对象只是把构造函数(Person4)的原型修改了,而实例的原型还是老的原型,即会切断实例与新
// 原型的联系,因此找不到新原型里的friends属性

//总结原型模式创建对象:
//优点:可以为实例提供共享的属性和方法,且有良好的封装性
//     (写成简单模式时要注意会出现的两点问题,加constructor属性和实例创建在原型定义之后即可避免)
//缺点:对每个实例无法一开始就定义自己的属性值,需要先定义好共享的属性值,再通过覆盖原型属性的方法修改成
//     自己的属性值

//4.组合使用构造函数模式和原型模式
function Person5(name, age) {
    this.name = name;
    this.age = age;
    this.privateFriends = ['a','b'];
}
Person5.prototype = {
  constructor: Person5,
  publicFriends: ['c','d'],
  sayName: function () {
      console.log(this.name);
  }
};

p1 = new Person5('Ann', 22);
p2 = new Person5('John', 26);
p1.publicFriends.push('s');
p1.privateFriends.push('v');

console.log(p2.publicFriends);  //[ 'c', 'd', 's' ]
console.log(p2.privateFriends);  //[ 'a', 'b' ]
console.log(p1.sayName === p2.sayName);  //true

//构造函数模式用来定义实例私有的属性,原型模式用来定义实例的方法和共享属性,该方法是目前最认可的一种创建
// 对象的方法

//5.动态原型模式
function Person6(name, age) {
    this.name = name;
    this.age = age;
    this.privateFriends = ['a','b'];
    if (typeof this.sayName === 'undefined') {
        console.log('create sayName');
        Person6.prototype.sayName = function () {
            console.log(this.name);
        }
    }
    if (typeof this.publicFriends === 'undefined') {
         console.log('create publicFriends');
        Person6.prototype.publicFriends = ['c', 'd'];
    }
}

p1 = new Person6('Ann', 22); //create sayName create publicFriends
console.log('-------------');
p2 = new Person6('John', 26); //未打印“create sayName create publicFriends”

//方法和共享属性的创建只会在第一次创建实例的时候调用,因为第一次调用完以后,原型对象就存在了该方法和共享
// 属性,后续实例会在原型对象中找到,便不会再次调用

//优点:避免了上一个方法独立的构造函数和原型的写法,把所有信息都封装在构造函数之中
//缺点:如果方法和共享属性过多,则需要写多个if语句来判断

//6.寄生构造函数模式:在已有的对象的基础上添加方法或属性创建满足需要的对象
//例如创建一个数组对象,该数组需要有以'+'连接元素为字符串的方法
function SpecialArr() {
    let arr = new Array();
    arr.push.apply(arr, arguments);
    arr.splitArr = function () {
        return arr.join('+');
    };
    return arr;
}

let specialArr = new SpecialArr('a','b','c');
console.log(specialArr.splitArr()); //a+b+c
console.log(specialArr instanceof Array);  //true
//specialArr的构造函数不是SpecialArray,SpecialArray只能看作其名义上的构造函数,其真正的构造函数是
//SpecialArray里返回的对象的真正的构造函数Array

//稳妥构造函数模式:新创建的实例方法不引用this,不使用new操作符构造函数
function Person7(name, age) {
    let o = new Object();
    o.sayName = function () {
        console.log(name);
    };
    return o;
}
p1 = Person7('Ann', 23);
console.log(p1.sayName()); //Ann  (只能通过sayName方法访问name)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值