javascript 创建 对象-前端开发:JavaScript 创建对象的 9 种形式

2023-08-26 0 8,632 百度已收录

ECMA-262 将对象定义为:“不需要属性的集合,其属性可以包含原始值、对象或函数。” 严格来说,这相当于说一个对象是一组没有特定顺序的值。 对象的每个属性或方法都有一个名称,每个名称都映射到一个值。 正因为如此,我们可以将 ECMAScript 对象视为哈希表:只不过是一组名称-值对,其中值可以是数据或函数。

创建自定义对象最简单的形式是创建一个Object的实例,然后为其添加属性和技能,如下所示:

var person = new Object();
person.name = "liubei";
person.age = 29;
person.job = "shayemuyou";
person.sayName = function(){
    alert(this.name);
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

上面的示例创建了一个名为 person 的对象,并为其添加了三个属性和一个技能。 其中sayName()方法用于显示name属性,this.name会被解析为person.name。 早期开发人员经常使用这种模式来创建对象。 后来,对象字面量方法成为创建对象的首选模式。 上述 case 中的对象字面量的句型可以写成如下:

var person = {
    name:"liubei",
    age:29,
    job:"shayemuyou",
    sayName:function(){
        alert(this.name);
    }
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

此示例中的 person 对象与上面的对象相同,具有相同的属性和技术。

虽然可以使用对象构造函数或对象字面量方法来创建单个对象,但这种方法有一个显着的缺点:使用同一个套接字创建许多对象会形成大量重复代码。 为了解决这个问题,人们开始使用鞋厂模型的变体。

1.工厂模式

工厂模式是软件工程领域著名的设计模式。 该模式体现了创建特定对象的过程。 考虑到在 ECMAScript 中创建类比较困难,开发人员发明了一个函数来封装使用特定套接字创建对象的细节,如下:

function createPerson(name, age, job){
    var o = new Object();
    o.name = name;
    o.age = age;
    o.job = job;
    o.sayName = function(){
        alert(this.name);
    }
    return o;
}
var person1 = createPerson("wei",25,"software");
var person2 = createPerson("bu",25,"software");
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

函数createPerson()可以根据接受的参数创建一个包含所有必要信息的Person对象。 该函数可以被多次调用,每次都会返回一个包含三个属性和一个方法的对象。 工厂模式看似解决了创建多个相似对象的问题,但它并没有解决对象识别的问题,即如何知道它是哪种对象类型。

2.构造函数模式

像 Array 和 Object 这样的原生构造函数将在运行时手动出现在执行环境中。 此外,我们可以创建自定义构造函数来定义自定义类型的属性和技术。 例如,我们可以使用构造函数重画前面的反例:

function Person(name, age, job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = function(){
        alert(this.name);
    }
}
var person1 = new Person("wei",25,"software");
var person2 = new Person("bu",25,"software");
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

在此反例中,Person() 函数替换了 createPerson() 函数。 我们注意到 Person() 和 createPerson() 之间的区别是:

console.log(person1.constructor == Person);     //true
console.log(person2.constructor == Person);     //true
 
 
  • 1
  • 2
  • 1
  • 2

对象的构造函数属性最初是用来指示对象类型的。 然而,当涉及到检查对象类型时,instanceof 运算符更可靠。 这个反例中我们创建的对象都是Object对象的实例,也是Person对象的实例,可以通过instanceof运算符来验证。

console.log(person1 instanceof Object);     //true
console.log(person1 instanceof Person);     //true
console.log(person2 instanceof Object);     //true
console.log(person2 instanceof Person);     //true
 
 
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

创建自定义构造函数意味着他的实例将来可以被标记为特定类型; 这就是构造函数模式比鞋工厂模式更好的地方。 在这个反例中,person1和person2之所以都是Object的实例,是因为所有对象都继承自Object。

构造函数的主要问题是每个方法都必须在实例上重新创建,导致视频内存的浪费。 在上面的反例中,person1 和 person2 都有一个名为 sayName() 的方法,但这两个方法不是同一个 Function 的实例。 不要忘记 ECMAScript 中的函数也是对象,因此每次定义函数时,都会实例化一个对象。 从逻辑上看,此时的构造函数可以定义如下:

function Person(name, age, job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = new Function("alert(this.name);")   //与声明函数在逻辑上是等价的
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

从这个角度看构造函数,更容易看出每个Person实例都包含着不同的Function实例的本质。 需要明确的是,这将导致不同的作用域链和标识符解析,但创建 Function 的新实例的机制保持不变。 因此,不同实例上的同名函数并不相等,下面的代码可以否认这一点。

alert(person1.sayName == person2.sayName);  //false
 
 
  • 1
  • 1

然而,实际上没有必要创建两个完成相同任务的 Function 实例; 而且,使用 this 对象,在执行代码之前无需将函数绑定到特定对象。 因此,可以通过将函数定义移到构造函数之外来解决这个问题,如下所示。

function Person(name, age, job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = sayName;
}
function sayName(){
    alert(this.name);
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

这样做解决了使用多个函数解决同一问题的问题,但产生了一个新问题:实际上仅由全局范围内的一个对象调用,这使得全局对象有点用词不当。 更让人难以接受的是:如果对象需要定义很多方法,那么就必须定义很多全局函数,所以我们自定义的引用类型根本就没有封装性。 幸运的是,此类问题可以使用原型模式来解决。

3.原型模式

我们创建的每个函数都有一个原型属性,它是一个指向对象的指针,其目的是包含可由特定类型的所有实例共享的属性和技巧。 使用原型对象的实例是为了让所有实例共享它所包含的属性和技能。 也就是说,不用在构造函数中定义对象的实例信息,而是可以直接将这些信息添加到原型对象中,如下所示:

function Person(){
}
Person.prototype.name = "wei";
Person.prototype.age = 27;
Person.prototype.job = "Software";
Person.prototype.sayName = function(){
    alert(this.name);
}
var person1 = new Person();
person1.sayName();      //"wei"
var person2 = new Person();
person2.sayName();      //"wei"
alert(person1.sayName == person2.sayName);
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

在这里,我们将 sayName() 方法和所有属性直接添加到 Person 的原型属性中,并将构造函数设为空函数。 即便如此,我们仍然可以通过构造函数创建新的对象,并且新的对象将具有相同的属性和技能。 但与构造函数不同的是,新对象的这个属性和方法是由所有实例共享的。 换句话说,person1 和 person2 都访问同一组属性和相同的 sayName() 函数。 要了解原型模式的工作原理,必须首先了解 ECMAScript 中原型对象的本质。

由于原型对象的本质太长javascript 创建 对象,我们将在下一章详细分析。 上面我们讲了原型模式的好处javascript 创建 对象,接下来我们看看原型模式的缺点。 原型模式省略了向构造函数传递参数,因此默认情况下所有实例都具有相同的属性值。 这会在一定程度上带来不便,而这并不是原型模式最大的问题,因为如果我们想给通过原型模式创建的对象添加属性,添加的属性会阻塞保存的同名属性原型对象。 也就是说,添加这个属性会阻止我们访问原型中的属性,但不会改变原型中的属性。

原型模式的最大问题是由其共享性质引起的。 原型中的所有属性都由许多实例共享。 这种共享非常适合功能。 它也适用于包含基本值的属性,但对于引用类型的属性值,问题更为突出。 让我们看下面的一个例子。 :

function Person(){
}
Person.prototype = {
    constructor:Person,
    name:"wei",
    age:29,
    friends:["乾隆","康熙"],
    sayName:function(){
        alert(this.name);
    }
}
var person1 = new Person();
var person2 = new Person();
person1.friends.push("嬴政");
console.log(person1.friends);   //["乾隆","康熙","嬴政"]
console.log(person2.friends);   //["乾隆","康熙","嬴政"]
console.log(person1.friends === person2.friends);   //true
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

在上面的示例中,Person.prototype 对象有一个名为friends 的属性,其中包含一个字符串字段。 然后创建 Person 的两个实例,然后更改 person1.friends 引用的字段,并向该字段添加一个字符串。 由于链表存在于Person.prototype而不是person1中,因此person2.friends也会发生变化。 但通常每个对象都必须有自己的属性,所以我们很少看到有人单独使用原型模式来创建对象。

四、构造函数模式与原型模式的结合

创建自定义类型的最常见形式是构造函数模式和原型模式的组合。 构造函数模式用于定义实例属性,原型模式用于定义方法和共享属性。 因此,每个实例都将拥有自己的实例属性副本,但同时共享对技巧的引用,从而最大限度地节省视频内存。 另外,这种混合模式还支持向构造函数传递参数; 可以说是两种模式中最好的。 下面的代码重绘了上面的情况:

function Person(name, age){
    this.name = name;
    this.age = age;
    this.friends = ["乾隆","康熙"];
}
Person.prototype = {
    constructor:Person,
    sayName:function(){
        alert(this.name);
    }
}
var person1 = new Person("wei",29);
var person2 = new Person("bu",25);
person1.friends.push("嬴政");
console.log(person1.friends);   //["乾隆", "康熙", "嬴政"]
console.log(person2.friends);   //["乾隆", "康熙"]
console.log(person1.friends === person2.friends);   //false
console.log(person1.sayName === person2.sayName);   //true
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

在这种情况下,实例属性在构造函数中定义,而属性构造函数和所有实例共享的技术 sayName() 则在原型中定义。 因此,更改 person1.friends 不会更改 person2.friends,因为它们引用了不同的字段。

这种混合构造函数和原型模式的模式是目前 ECMAScript 中使用最广泛、最受认可的创建自定义类型的方式。 可以说这是定义引用的默认方式。

5.动态原型模式

具有其他 OO 语言经验的开发人员可能会发现听到单独的构造函数和原型特别痛苦。 动态原型模式就是解决这个问题的方法。 它将所有信息封装在构造函数中,并通过构造函数初始化原型(仅在必要时),并保持了同时使用构造函数和原型的优点。 换句话说,您可以通过检查应该存在的方法是否有效来决定是否初始化原型。 让我们看一个例子:

function Person(name, age){
    this.name = name;
    this.age = age;
    this.friends = ["乾隆","康熙"];
    //注意if语句
    if(typeof this.sayName!="function"){
        Person.prototype.sayName = function(){
            alert(this.name);
        }
    }
}
var person1 = new Person("wei",29);
person1.friends.push("嬴政");
person1.sayName();
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

请注意构造函数代码中的 if 语句,这里的 sayName() 方法仅在原型尚不存在时才添加到原型中。 该代码块仅在第一次调用构造函数时才会执行。 之后,原型已经初始化,不需要进一步更改。 但请记住,此处所做的更改会立即反映在所有实例中。 所以,这个方法可以说是非常完美的。 其中,if语句检测初始化后应该存在的任何方法和属性——你不必使用大量的if来检测每个属性和技能,只需检测其中之一即可。 对于这种模式创建的对象,还可以使用instanceof操作符来确定他的类型。

注意:使用动态原型模式时,不能使用对象字面量重绘原型。 如果在已经创建实例的情况下重绘原型,则现有实例和新原型之间的连接将被切断。

6. 寄生构造函数模式

一般来说,寄生构造函数模式可以在上述模式都不适用的情况下使用。 这种模式的基本思想是创建一个函数,简单地封装创建对象的代码,然后返回新创建的对象,但从表面上看,这个函数看起来像一个典型的构造函数。 我们来看一个反例:

function Person(name, age, job){
    var o = new Object();
    o.name = name;
    o.age = age;
    o.job = job;
    o.sayName = function(){
        alert(this.name);
    }
    return o;
}
var person = new Person("wei",29,"banzhuan");
person.sayName();   //"wei"
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

在此反例中,Person 函数创建一个对象,使用适当的属性和技术初始化该对象,然后返回该对象。 该模式与鞋工厂模式没有太大区别,只不过使用 new 运算符将使用的包装函数调用为构造函数。 如果构造函数不返回值,则默认返回新对象的实例。 相反,通过在构造函数末尾添加 return 语句,您可以重绘调用构造函数时返回的值。

在特殊情况下可以使用此模式来创建对象的构造函数。 假设我们想创建一个带有额外方法的特殊字段。 由于无法直接更改数组构造函数,因此可以使用以下模式:

function SpecialArray(){
    //创建数组
    var values = new Array();
    //添加值
    values.push.apply(values,arguments);
    //添加方法
    values.toPipedString = function(){
        return this.join("|");
    }
    //返回数组
    return values;
}
var colors = new SpecialArray("red","blue","green");
console.log(colors.toPipedString());    //red|blue|green
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

在这个反例中,我们创建一个名为 SpecialArray 的构造函数。 在这个函数内部,首先创建一个链表,然后push()方法初始化链表的值。 然后在链表实例中添加toPipedString()方法,返回由竖线分隔的链表值。 最后,链表作为函数返回。 接下来,我们调用 SpecialArray 构造函数,传入初始化值,并调用 toPipedString() 方法。

关于寄生构造函数模式,需要声明一件事:第一,返回的对象与构造函数或构造函数的原型无关; 也就是说,构造函数返回的对象与构造函数外部创建的对象没有什么不同。 因此,不能依赖instanceof 运算符来确定对象的类型。 由于此问题,我们建议在可以使用其他模式时不要使用这些模式。

七、安全构造函数模式

道格拉斯·克拉克福德 (Douglas Clarkford) 在 中发明了稳定物体的概念。 所谓稳定的对象是指它没有公共属性,它的技术也不涉及这个对象。 安全对象最好用在严格禁止使用 this 和 new 的安全环境中,或者当数据受到保护以防止其他应用程序更改时。 安全构造函数遵循与寄生构造函数类似的模式,但有两点不同:一是新创建对象的实例方法不引用 this;二是新创建的对象的实例方法不引用 this。 另一个是构造函数不是使用new运算符调用的。 根据稳定构造函数的要求,上面的Person构造函数可以重绘如下:

function Person(name, age, job){
    //创建要返回的新对象
    var o = new Object();
    //可以在这里定义私有变量和函数
    //添加方法
    o.sayName = function(){
        alert(this.name);
    };
    //返回对象
    return o;
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

请注意,在以这些模式创建的对象中,除了使用 sayName() 方法之外,无法访问 name 的值。 您可以像这样使用健壮的 Person 构造函数:

var person =Person("weiqi",22,"banzhuan");
person.sayName();   //weiqi
 
 
  • 1
  • 2
  • 1
  • 2

这样,变量person中保存的是一个稳定的对象,除了sayName()方法之外,没有其他方法可以访问其他数据成员。 即使其他代码向对象添加方法或数据成员,也没有其他方法可以访问传递到构造函数中的原始数据。 安全构造函数模式提供的安全性使其特别适合在个人安全执行环境中使用 - 例如 ADsafe() 提供的环境。

注意:与寄生构造函数模式类似,使用安全构造函数模式创建的对象与构造函数之间没有任何关系,因此instanceof运算符对于这些对象没有任何意义。

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

悟空资源网 javascript javascript 创建 对象-前端开发:JavaScript 创建对象的 9 种形式 https://www.wkzy.net/game/151281.html

常见问题

相关文章

官方客服团队

为您解决烦忧 - 24小时在线 专业服务