基于JS对象创建常用方式及原理分析

前言

俗话说“在js语言中,一切都对象”,而且创建对象的方式也有很多种,所以今天我们做一下梳理

最简单的方式

JavaScript创建对象最简单的方式是:对象字面量形式或使用Object构造函数

对象字面量形式

var person = new Object();
  person.name = "jack";
  person.sayName = function () {
  alert(this.name)
}

使用Object构造函数

var person = {
  name: "jack";
  sayName: function () {
    alert(this.name)
  }
}

明显缺点:创建多个对象时,会出现代码重复,于是乎,‘工厂模式'应运而生

工厂模式

通俗一点来理解工厂模式,工厂:“我创建一个对象,创建的过程全由我来负责,但任务完成后,就没我什么事儿了啊O(∩_∩)O哈哈~”

function createPerson (name) {
  var o = new Object();
  o.name = name;
  o.sayName = function () {
    alert(this.name)
  }
  return o
}

var p1 = new createPerson("jack");

明显缺点:所有的对象实例都是`Object`类型,几乎类型区分可言啊!你说无法区分类型,就无法区分啊,我偏不信!那咱们就来看代码吧:

var p1 = new createPerson("jack");
var p2 = new createPerson("lucy");

console.log(p1 instanceof Object); //true
console.log(p2 instanceof Object); //true

你看,是不是这个理儿;所以为了解决这个问题,我们采用‘构造函数模式'

构造函数模式

构造函数模式,就是这个函数我只管创建某个类型的对象实例,其他的我一概不管(注意到没有,这里已经有点类型的概念了,感觉就像是在搞小团体嘛)

function Person (name) {
  this.name = name;
  this.sayName = function () {
    alert(this.name)
  }
}

function Animal (name) {
  this.name = name;
  this.sayName = function () {
    alert(this.name)
  }
}

var p1 = new Person("jack")
p1.sayName() //"jack"

var a1 = new Animal("doudou")
a1.sayName() //"doudou"

console.log(p1 instanceof Person) //true
console.log(a1 instanceof Animal) //true
console.log(p1 instanceof Animal) //false(p1显然不是Animal类型,所以是false)
console.log(a1 instanceof Person) //false(a1也显然不是Person类型,所以同样是false)

上面这段代码证明:构造函数模式的确可以做到对象类型的区分。那么该模式是不是已经完美了呢,然而并不是,我们来一起看看下面的代码:

 //接着上面的代码
 console.log(p1.sayName === a1.sayName) //false

发现问题了吗?`p1`的`sayName`竟然和`a1`的`sayName`不是同一个,这说明什么?说明‘构造函数模式'根本就没有‘公用'的概念,创建的每个对象实例都有自己的一套属性和方法,‘属性是私有的',这个我们可以理解,但方法你都要自己搞一套,这就有点没必要了

明显缺点:上面已经描述了,为了解决这个问题,又出现了一种新模式‘原型模式',该模式简直就是一个阶段性的跳跃,下面我们来看分一下‘原型模式'

原型模式

这里要记住一句话:构造函数中的属性和方法在每个对象实例之间都不是共享的,都是各自搞一套;而要想实现共享,就要将属性和方法存到构造函数的原型中。这句话什么意思呢?下面我们来详细解释

当建立一个构造函数时(普通函数亦然),会自动生成一个`prototype`(原型),构造函数与`prototype`是一对一的关系,并且此时`prototype`中只有一个`constructor`属性(哪有,明明还有一个`__proto__`呢,这个我们先不在此讨论,后面会有解释)

这个`constructor`是什么?它是一个类似于指针的引用,指向该`prototype`的构造函数,并且该指针在默认的情况下是一定存在的

console.log(Person.prototype.constructor === Person) //true

刚才说过`prototype`是`自动生成`的,其实还有另外一种手动方式来生成`prototype`:

function Person (name) {
  this.name = name
}
Person.prototype = {
  //constructor: Person,
  age: 30
}
console.log(Person.prototype) //Object {age: 30}
console.log(Person.prototype.constructor === Person) //false

Tips:为了证明的确可以为构造函数手动创建`prototype`,这里给`prototype`加了`name`属性。

可能你已经注意到了一个问题,这行代码:

console.log(Person.prototype.constructor === Person) //false

结果为什么是`false`啊?大哥,刚才的`prototype`是默认生成的,然后我们又用了另外一种方式:手动设置。具体分析一下手动设置的原理:

1.构造函数的`prototype`其实也是一个对象

2.当我们这样设置`prototype`时,其实已经将原先`Person.prototype`给切断了,然后又重新引用了另外一个对象

3.此时构造函数可以找到`prototype`,但`prototype`找不到构造函数了

Person.prototype = {
  //constructor: Person, // 因为constructor属性,我没声明啊,prototype就是利用它来找到构造函数的,你竟然忘了声明
  age: 30
}

4.所以,要想显示手动设置构造函数的原型,又不失去它们之间的联系,我们就要这样:

function Person (name) {
  this.name = name
}
Person.prototype = {
  constructor: Person, //constructor一定不要忘了!!
  age: 30
}

画外音:“说到这里,你还没有讲原型模式是如何实现属性与方法的共享啊”,不要急,马上开始:

对象实例-构造函数-原型,三者是什么样的关系呢?

看明白这张图的意思吗?

1.当对象实例访问一个属性时(方法依然),如果它自身没有该属性,那么它就会通过`__proto__`这条链去构造函数的`prototype`上寻找

2.构造函数与原型是一对一的关系,与对象实例是一对多的关系,而并不是每创建一个对象实例,就相应的生成一个`prototype`
这就是原型模式的核心所在,结论:在原型上声明属性或方法,可以让对象实例之间共用它们

然后原型模式就是完美的吗?并不是,它有以下两个主要问题:

问题1:如果对象实例有与原型上重名的属性或方法,那么,当访问该属性或方法时,实例上的会屏蔽原型上的

function Person (name) {
  this.name = name
}
Person.prototype = {
  constructor: Person,
  name: 'lucy'
}
var p1 = new Person('jack');
console.log(p1.name); //jack

问题2:由于实例间是共享原型上的属性和方法的,所以当其中一个对象实例修改原型上的属性(基本值,非引用类型值或方法时,其他实例也会受到影响

原因就是,当实例自身的基本值属性与原型上的重名时,实例就会创建该属性,留着今后自己使用,而原型上的属性不会被修改;但如果属性是引用类型值,如:`Array`、`Object`,当发生重名时,实例是不会拷贝一份新的留给自己使用的,还是坚持实例间共享,所以就会出现上图中的情况

以上两个问题就是原型模式的明显缺点,为了改掉这些缺点,我们一般会采用一种组合模式“组合使用构造函数模式和原型模式”,其实在原型模式这一节,该模式已经有所应用了

组合使用构造函数模式和原型模式

这种模式可谓是集构造函数模式和原型模式之所长,用构造函数模式来定义对象实例的属性或方法,而共享的属性或方法就交给原型模式

function Person (name) {
  this.name = name //实例的属性,在构造函数中声明
}

Person.prototype = {
  constructor: Person,
  sayName: function () { //共享的方法存在原型中
    alert(this.name)
  }
}

注:此模式目前是ECMAScript中使用最广泛、认同度最高的一种创建自定义类型的方法

-----------------

下面要介绍的几个模式是针对不同场景的,而不是说`组合使用构造函数模式和原型模式`有什么缺点,又用这几个模式来弥补,不是这样的

动态原型模式

特点:共享的方法是在构造函数中检测并声明的,原型并没有被显示创建

function Person (name) {
  this.name = name;
  if (typeof this.sayName !== 'function') { //检查方法是否存在
    console.log('sayName方法不存在')
    Person.prototype.sayName = function () {
      alert(this.name)
    }
  } else {
    console.log('sayName方法已存在')
  }
}

var p1 = new Person('jack'); //'sayName方法不存在'
p1.sayName(); //因为sayName不存在,我们来创建它,所以这里输出'jack'
var p2 = new Person('lucy'); //'sayName方法已存在'
p2.sayName(); //这时sayName已存在,所以输出'lucy'

当`Person`构造函数第一次被调用时,`Person.prototype`上就会被添加`sayName`方法;《Javascript高级程序设计》一书说到:使用动态原型模式时,不能使用对象字面量重写原型。我们来理解一下:

分析:

1.`p1`实例创建,此时原型没有`sayName`方法,那我们就为原型添加一个

2.随后,我们以字面量的形式重写了原型,这时旧的原型并没有被销毁,而且它和`p1`还保持着联系

3.之后的实例,也就是这里的`p2`,都是与新原型保持联系;所以`p1`、`p2`有各自的构造器原型,即使它们的构造器是同一个

所以切记:当我们采用动态原型模式时,千万不要以字面量的形式重写原型

寄生构造函数模式

了解此模式之前,我们先来想一个问题:构造函数为什么要用`new`关键字调用?代码说话:

我们发现什么?如果不是`new`方法调用构造函数,那么就要显式的`return`,否则构造函数就不会有返回值;但如果使用`new`,那就没有这个问题了

下面我们再来看寄生构造函数模式:

function Person (name) {
  var o = new Object();
  o.name = name;
  o.sayName = function () {
    alert(this.name)
  };
  return o
}

var p1 = new Person('jack'); //与工厂模式唯一不同之处:使用new调用
p1.sayName(); //jack

其实new不new都无所谓,因为我们已经显式的return o

那么寄生构造函数模式到底有什么应用场景呢?据《javascript高级程序设计》一书记载,举例:如果我们想创建一个具有额外方法的特殊数组,那么我们可以这样做:

function SpecialArray () {
  var values = new Array();
  Array.prototype.push.apply(values,arguments);
    values.toPipedString = function () {
    return this.join('|')
  }
  return values
}

var colors = new SpecialArray('red','blue','green');
alert(colors.toPipedString()) //'red|blue|green'

最后重要的一点:该模式和构造函数和原型无缘,也就是不能区分实例类型,因为该模式生成的实例,它的构造函数都是Object,原型都是Object.prototype

稳妥构造函数模式

该模式与寄生构造函数相比,主要有两点不同:

1.创建对象实例的方法不引用this

2.不使用new操作符调用构造函数

按照稳妥构造函数的要求,可以将前面的Person构造函数重写如下:

function Person (name) {
  var o = new Object();
  o.sayName = function () {
    alert(name) //这里其实涉及到了闭包的知识,因此产生了私有属性的概念
  }
  return o
}

此模式最适合在一些安全的环境中(这些环境中会禁止使用this和new),同理,此模式与构造函数和原型也无缘

结语

以上就是对js中创建对象的方式的总结,希望对大家有所帮助

这篇基于JS对象创建常用方式及原理分析就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • JavaScript中创建对象和继承示例解读

    对象创建: 当一个函数对象被创建时候,Function构造器产生的函数对象会运行类似这样的代码: 复制代码 代码如下: this.prototype={constructor:this}; 假设函数F F用new方式构造对象时,对象的constructor被设置成这个F.prototype.constructor 如果函数在创建对象前修改了函数的prototype,会影响创建出来对象的construtor属性 如: 复制代码 代码如下: function F(){}; F.prototype={

  • javascript创建对象的3种方法

    本文为大家分享了js创建对象的多种方法,分享个方法的优劣,具体内容如下 第一种创建对象的方式: 创建JSON对象 推荐使用的场合: 作为函数的参数,临时只用一次的场景.比如设置函数原型对象. var object = { name: "Eric", age: 24, sayHi: function(){ console.log("My name is "+ this.name + " this year is " + this.age + &qu

  • js面向对象 多种创建对象方法小结

    开始创建对象: 1.对象字面量. 复制代码 代码如下: var clock={ hour:12, minute:10, second:10, showTime:function(){ alert(this.hour+":"+this.minute+":"+this.second); } } clock.showTime();//调用 2.创建Object实例 复制代码 代码如下: var clock = new Object(); clock.hour=12; cl

  • JavaScript中创建对象的7种模式详解

    ECMA-262把对象定义为:"无需属性的集合,其属性可以包含基本值.对象或者函数."严格来讲,这就相当于说明对象是一组没有特定顺序的值.对象的每个属性或方法都有一个名字,而每个名字都映射到一个值.正因为这样,我们可以把ECMAScript的对象想象成散列表:无非就是一组名对值,其中值可以是数据或函数. 创建自定义对象最简单的方式就是创建一个Object的实例,然后再为他添加属性和方法,如下所示: var person = new Object();.person.name = &qu

  • 基于JS对象创建常用方式及原理分析

    前言 俗话说"在js语言中,一切都对象",而且创建对象的方式也有很多种,所以今天我们做一下梳理 最简单的方式 JavaScript创建对象最简单的方式是:对象字面量形式或使用Object构造函数 对象字面量形式 var person = new Object(); person.name = "jack"; person.sayName = function () { alert(this.name) } 使用Object构造函数 var person = { na

  • JavaScript实现创建自定义对象的常用方式总结

    本文实例讲述了JavaScript实现创建自定义对象的常用方式.分享给大家供大家参考,具体如下: 1. 对象字面量方式 对象字面量方式是创建自定义对象的首选模式,简单方便. var per = { name:'zhangsan', age:25, job:'html', sayName:function(){ alert(this.name); } } 缺点:使用同一个接口创建很多对象,会产生大量的重复代码.比如我想再创建一个per1对象,我就得把上面的代码再重新写一遍,改变不同的属性值. 2.

  • 基于js对象,操作属性、方法详解

    一,概述 在Java语言中,我们可以定义自己的类,并根据这些类创建对象来使用,在Javascript中,我们也可以定义自己的类,例如定义User类.Hashtable类等等. 目前在Javascript中,已经存在一些标准的类,例如Date.Array.RegExp.String.Math.Number等等,这为我们编程提供了许多方便.但对于复杂的客户端程序而言,这些还远远不够. 与Java不同,Java2提供给我们的标准类很多,基本上满足了我们的编程需求,但是Javascript提供的标准类很

  • JS对象创建与继承的汇总梳理

    目录 引言 对象创建 工厂函数 构造函数 构造+原型 工厂+构造+原型 ES6 class 对象与函数 小结 引言 在 6 月更文中零零散散讲了 JS 的对象创建和对象继承,有工友对此还是表示疑惑,要注意:这是两个不同但又相关的东西,千万别搞混了! 这些文章是: 蓦然回首,“工厂.构造.原型”设计模式,正在灯火阑珊处 JS精粹,原型链继承和构造函数继承的 “毛病” “工厂.构造.原型” 设计模式与 JS 继承 JS 高级程序设计 4:class 继承的重点 JS class 并不只是简单的语法糖

  • JVM对象创建和内存分配原理解析

    创建对象 当 JVM 收到一个 new 指令时,会检查指令中的参数在常量池是否有这个符号的引用,还会检查该类是否已经被加载过了,如果没有的话则要进行一次类加载. 接着就是分配内存了,通常有两种方式: 指针碰撞 空闲列表 使用指针碰撞的前提是堆内存是完全工整的,用过的内存和没用的内存各在一边每次分配的时候只需要将指针向空闲内存一方移动一段和内存大小相等区域即可. 当堆中已经使用的内存和未使用的内存互相交错时,指针碰撞的方式就行不通了,这时就需要采用空闲列表的方式.虚拟机会维护一个空闲的列表,用于记

  • Spring复杂对象创建的方式小结

    在Spring中,对于简单类型的创建,我们可以使用set注入和构造注入.但是对于复杂类型的如何创建? 什么是复杂类型,比如连接数据库的Connection对象,以及Mybatis中的SqlSessionFactory对象.在以前我们是通过这种方式获取Connection对象的: Connection conn = null; try { Class.forName("com.mysql.cj.jdbc.Driver"); conn = DriverManager.getConnecti

  • js 对象使用的小技巧实例分析

    本文实例讲述了js 对象使用的小技巧.分享给大家供大家参考,具体如下: js中中,Object,Array ,Function 等都属于引用类型,他们的变量名都是指向对象的指针. 这样就有一个好处,当处理一个复杂json树的时候,想要单独改变其中某一个子对象属性时,不需要根据对象id遍历查找到这个对象了,而是可以直接通过事件方式将这个对象通过参数的方式赋值给一个专属变量,这个变量就指向这个对象,这样就可以随意改变对象属性了.改变这个变量对应的对象,整个json树中的这个对象也被相应的改变. 下面

  • PHP对象链式操作实现原理分析

    本文实例讲述了PHP对象链式操作实现原理.分享给大家供大家参考,具体如下: 什么是链式操作呢?使用jQuery的同学印象应该会很深刻.在jQuery中,我们经常会这样的来操作DOM元素: $("p").css("color").addClass("selected"); 连贯操作看起来的确很酷,也非常的方便代码的阅读.那么在PHP里面是否可以实现呢?答案是肯定的,当然了必须是在OOP中用才行,在过程化的程序中,就没有必要用这种方法了. 在PHP中

  • js中数组排序sort方法的原理分析

    本文实例分析了js中数组排序sort方法的原理.分享给大家供大家参考.具体分析如下: 最近在百度的项目中要用到对数组进行排序,当然一开始自然想到了数组的sort方法,这方法应用非常简单,大致如下: 复制代码 代码如下: window.onload=function(){         var arr=[2,55,55,1,75,3,9,35,70,166,432,678,32,98];         var arr2=["George","John","

  • JS对象创建的几种方式整理

    最近一直在看JS高级程序设计这本书,有空来梳理一下几种创建对象的方式.话不多说,直接步入正题. 第一种:Object构造函数创建 var Person = new Object(); Person.name = 'Nike'; Person.age = 29; 这行代码创建了Object引用类型的一个新实例,然后把实例保存在变量Person中. 第二种:使用对象字面量表示法 var Person = {};//相当于var Person = new Object(); var Person =

随机推荐