03 - JavaScript对象

175 阅读13分钟

ES5

  • 对象:JavaScript最基本的的数据类型之一,是一种复合的数据类型。
  • 数组:JavaScript中唯一用来存储和操作有序数据集的数据结构。

1. 创建对象的方法

JavaScript对象是拥有属性和方法的数据。

创建方法:

  • 使用内置对象
  • 直接定义
  • 自定义创建

1. 使用内置对象

JavaScript可用的内置对象分为两种:

  1. 语言级对象:String、Object、Function etc. (见数据类型篇
  2. 环境宿主级对象:window、document、body etc.
通常使用内置对象就是用语言级对象的构造方法,创建出一个新对象。




2. 直接定义并创建对象 - 采用“键/值对”集合的形式

例如上面这个例子中的person1。

这种方法的好处是:

  • 易于阅读和编写,符合人们的读写习惯
  • 易于机器对其解析和生成、分析和运行
  • 格式化的数据交换

3. 自定义对象构造创建

创建高级对象构造有2种方式:

  • 使用this关键字构造
    • function定义,function内部用this定义好属性和方法,然后用new来创建一个object
  • 使用prototype构造
    • function定义,function内部没有定义好属性和方法,function外用prototype来定义属性和方法,最后用new来创建一个object


由此可见:

  • 用new function时,this指向创建的对象本身(如mySon),而regular function时,指向的是window(如myNiece)。
  • 由mySon和myDaughter可见,this与prototype定义的一个不同点就是“属性的占用空间不同”。
    • 使用this关键字,示例初始化时为每个实例开辟构造方法所包含的所有属性、方法所属的空间。
    • 使用prototype定义,由于prototype实际上是指向父级的一种引用,仅仅是数据的副本,因此在初始化及存储上都比this节约资源。

2. 常用内置对象

  • String
  • Array
  • Date
  • Boolean
  • Math
  • Number

2.1  String

String是JavaScript的内置对象,属于动态对象,需要创建对象实例后才能引用该对象的属性和方法,该对象主要用于:

  • 处理或格式化文本字符串
  • 确定和定位字符串中的子字符串

创建String对象,2种方法

  1. var txt = "string";
  2. var txt = new String("string");

Normally, JavaScript strings are primitive values, created from literals:

var firstName = "John";

But strings can also be defined as objects with the keyword new:

var firstName = new String("John");

Don't create strings as objects. It slows down execution speed.
The new keyword complicates the code. This can produce some unexpected results:


txt0 == txt2得到true是因为txt0和txt2有相同values。

txt2 == txt3得到false,txt2 === txt3得到false,是因为Comparing two JavaScript objects will always return false.


String对象属性

  • constructor:对创建该对象的函数的引用
  • length:字符串的长度
  • prototype:允许用户向对象添加属性和方法

String对象方法

  

Primitive values, like "John Doe", cannot have properties or methods (because they are not objects).
But with JavaScript, methods and properties are also available to primitive values, because JavaScript treats primitive values as objects when executing methods and properties.




2.2  Array

JavaScript arrays are used to store multiple values in a single variable.

创建数组有3种方法:

  1. 常规方法:

    var myCars = new Array();
    myCars[0] = "Saab";
    myCars[1] = "Volvo";
    myCars[2] = "BMW";
  2. 简洁方法:

    var myCars = new Array("Saab", "Volvo", "BMW");
  3. 字面方法:

    var myCars = ["Saab", "Volvo", "BMW"];


Array Elements Can Be Objects

JavaScript variables can be objects. Arrays are special kinds of objects.

Because of this, you can have variables of different types in the same Array.

You can have objects in an Array. You can have functions in an Array. You can have arrays in an Array:


Array对象属性

Array对象方法


 


The every() method checks if all elements in an array pass a test (provided as a function).

The every() method executes the function once for each element present in the array:

  • If it finds an array element where the function returns a
    false value, every() returns false (and does not check the remaining values)
  • If no false occur, every() returns true

Note: every() does not execute the function for array elements without values.

Note: every() does not change the original array





The find() method returns the value of the first element in an array that pass a test (provided as a function).

The find() method executes the function once for each element present in the array:

  • If it finds an array element where the function returns a
    true
    value, find() returns the value of that array element (and does not check the remaining values)
  • Otherwise it returns undefined

Note: find() does not execute the function for empty arrays.

Note: find() does not change the original array.



The Array.from() method returns an Array object from any object with a length property or an iterable object.



Array.from(string) 从string变成array数组.join() 把数组变成string



push()pop()是从尾端添加或删除。


unshift()shift()是从头部添加或删除。

slice()只能截取splice()除了截取还能添加






3. 对象访问语句

1. for...in循环语句(遍历对象的每一个属性)


由此可见:

  • for...in内部的var属于global variable
  • var声明的是数组的一个元素或者对象的一个属性。in后面的是一个对象名,或者计算结果为对象的表达式。该语句用来遍历对象的每一属性,每次都会将属性名作为字符串保存在变量中。

2. with语句

有了with语句,在存取对象属性和方法时就不用重复指定参考对象。


由此可见,在with语句块中,凡是JavaScript不能识别的属性和方法都和该语句块指定的对象有关。

4. 对象的序列化(即将对象的状态转为字符串)

对象序列化是指对象的状态转化为字符串,从而存储在计算机中。

如何序列化对象?

JavaScript JSON的方法:

  • JSON.stringify() -> 序列化对象
  • JSON.parse() -> 还原JavaScript对象,进行对象的反序列化

JSON(JavaScript Object Notation):基于JavaScript编程语言标准的一种轻量级的数据交换格式,主要用于与服务器进行数据交换。跟XML一样,JSON是独立语言,在跨平台数据传输上有很大的优势。

为什么会有对象序列化?

主要从2点应用考虑:

  1. 本地存储对象
    • var obj = { x: 1, y: 2 }
      

    •  以上这个语句在运行的时候,{ x: 1, y:  2 }对象内容会存储在一块内存中,而obj变量也是存储在内存中,变量本身存储的还是该对象的地址引用。栈存放变量,堆存放复杂对象,池存放常量,所以也叫常量池
    •   简单的说,对象花括号里的东西就是程序在计算机通电时,在内存中维护的一种东西,如果程序停止运行了,或者是计算机断电了,对象obj花括号里的东西将不复存在。
    • 如何将对象obj花括号里的内容长久的保存以供程序员连续使用?就要将其保存在磁盘上。
    • 如何将对象保存在磁盘上?将对象序列化。
      • 将对象的内容转换成一个字符串形式,然后再保存在磁盘上。
  2. HTTP传输对象
    • 如何通过HTTP协议把对象的内容发送到客户端?
      • 先把对象obj序列化,然后客户端根据接收到的字符串再反序列化,也就是将字符串还原为对象,从而解析出相应的对象。
      • 此乃对象序列化和反序列化的意义所在。

对象序列化实操 - JSON.stringify()

JSON.stringify(value[, replacer[, space]])
  • value:必选项,是指一个有效的JSON字符串
  • replacer:可选项,用于转换结果的函数或者数组
    • 如果replacer是一个函数
      • 它首先会被该对象调用一次,然后该对象的每个属性会调用一次,每次都会给这个函数传递两个值,key和value。想在序列化过程中跳过某个key,只需要返回undefined,否则返回提供的value
    • 如果replacer是一个数组
      • 它应该是一个字符串数组,每一个值都指定了对象的属性名称,代表属性应该被加入到序列化中,如果一个属性不在这个列表中,它会被跳过。
  • space:可选项,文本添加锁进、空格和换行符。
    • 如果space是一个数字
      • 则返回值文本在每个级别缩进指定数目的空格。
      • 如果space > 10,则文本缩进10个空格。
    • 如果space非数字
      • 如果space是例如 \t,返回值:返回包含JSON文本的字符串
      • 如果space是个字符串,其值的前十个字符被用于每个缩进层次

space


replacer


  • 由str_pretty3和str_pretty4,str_pretty4和str_pretty5可见,
    • 如需返回value值,一定要在函数中return value,如不需要返回value值;
    • 如需要跳过它,则返回undefined;
    • 如不返回这条[key, value]对,则省略返回语句;
    • 因为replacer函数的原理是:
      • 如果replacer是一个函数,它首先会被该对象调用一次,然后该对象的每个属性会调用一次,每次都会给这个函数传递两个值,key和value。
      • 想在序列化过程中跳过某个key,只需要返回undefined,否则返回提供的value。


  • replacer 放在JSON.stringify()以外,需要让JSON.stringify()内的str对象调用replacer函数,replacer函数就要绑定这个对象,否则replacer函数就无法直接调用str对象的key和value。
  • regular function绑定对象有call, apply, bind三种方法。
    • call和apply方法基本相同,在这里除了对象str外,也没有其它参数,所以这里call和apply是一样的用法。
    • 但是,replacer函数的原理是:对象str调用一次replacer函数,然后str的每个key再调用replacer函数一次。所以,像call和apply这种直接运行,且只运行一次就出结果的函数,在这里不能达到目的,必须用bind函数达到绑定regular function和对象str的目的。

JSON 不允许包含函数,JSON.stringify() 会删除 JavaScript 对象的函数,包括 key 和 value。


对象反序列化实操 - JSON.parse()

JSON.parse(text[, reviver]);

  • text必选项,是指一个有效的JSON字符串
  • reviver:可选项,一个转换结果的函数,将为对象的每个成员调用此函数
  • 返回值:返回给定JSON字符串转换后的对象


使用 JSON.parse 的 reviver 函数时一定要注意遍历到最后的顶层对象 key 为 "",需要返回 value。不然报错。


如何深度遍历有子对象的对象?


利用深度/广度优先遍历手动实现JavaScript对象的深度拷贝

搞不懂JS中赋值·浅拷贝·深拷贝的请看这里

5. 创建对象的常用模式

  1. 工厂模式
  2. 自定义构造函数模式
  3. 原型模式
  4. 原型模式和构造函数模式
  5. 动态原型模式

5.1 工厂模式

工厂模式,就是一个类似于机器的方法,只要把原料(参数)放入机器,经过机器加工,就能获得想要的对象。

由于无法在ES5中创建类,开发人员就发明了一种函数,用来封装以特定接口创建对象的细节。


line 23产生error是因为employee是由var定义在function内的变量,其具有function(local) scope,出了function就访问不到了。

由employee1 === undefined, employee2 === undefined, employee1 === employee2为true可见,undefined === undefined。


故而不要漏掉return语句,以便外界访问到在函数内创建的这个object。

employee1和employee2为两个不同的object。


由line 21和line 26可见,console.log打印的时候,如果前有字符串,则字符串调用其本身方法toString()打印成字符串:

使用工厂模式的注意事项

  • 引用该对象的时候,使用的是“ var employee1 = createEmployee("Jim", 32, "frontend developer"); “ 而不是” var employee1 = new CreateEmployee("Jim", 32, "frontend developer"); “,因为后者可能会出现很多问题。
    • 前者是经典工厂模式
    • 后者是混合工厂模式(不推荐使用此方法)
  • 一定不要忘记在函数最后返回函数内部创建的对象引用给外界!
  • 不推荐使用此方法创建对象,但是应该了解!

工厂模式的缺点:

工厂模式虽然解决了创建多个相似对象的问题,但是却没有解决对象识别的问题(即怎样知道一个对象的类型)。


5.2 自定义构造函数模式

ECMAScript中的构造函数可以用来创建特定类型的对象


程序执行到该行代码,还为调用构造函数时,employee3还没有被创建出来。


调用构造函数,当程序运行到构造函数结束时,可见参数都被一一赋值为this对象的属性上。

与工厂模式的不同之处在于:

  • 没有显示地创建对象
  • 直接将属性和方法赋值给了this对象
  • 没有return语句
  • 要创建Employee示例,必须使用new。调用构造函数,实际会经历4个步骤:
    1. 创建一个新对象
    2. 将构造函数的作用域赋给这个新对象(因此this就指向了这个新对象
    3. 执行构造函数中的代码(为这个新对象添加属性)
    4. 返回新对象
一定要用new!

Employee3



Employee4




用new时,this指向创建出来的对象本身;不用new时,就是调用regular function,this指向global对象(browser里就是window对象)。

可以用call()或者apply()






自定义构造函数的优缺点:

优点:它解决了工厂函数“没有解决对象识别的问题”。


检测对象类型,还是instanceof操作符更靠谱些。

创建的对象既是Object的实例,同时也是Employee的实例。

这个Employee实例具有一个constructor(构造函数)属性,该属性指向Employee。


而工厂模式里,指向的是Object。


同工厂模式一样,不推荐使用这种模式创建对象,但是仍需要了解。


缺点:每个被创建出来的employee实例都包含一个不同的work Function实例。


在ECMAScript中的函数是对象,因此每定义一个函数,也就是实例化了一个对象(函数对象)。从逻辑角度讲,此时的构造函数也可以这样定义。因此,不同实例上的同名函数是不相等的两个函数对象实例

然而,创建两个完成同样任务的Function实例的确没有必要,况且有this对象在,根本不用在执行代码前就把函数绑定在特定对象上面。因此,建议把函数定义移到构造函数外来解决这个问题。


把work函数移到构造函数外,此work函数就挂载在全局对象window上。而在构造函数内,将work属性设置成等于全局的work函数。这样由于Employee.work包含的时一个指向函数的指针,因此employee3和employee4对象就共享了在全局作用域中定义的同一个work函数

这样做确实解决了两个函数实例做同一件事的问题,可是新问题又来了:

在全局作用域中定义的函数实际上只能被某个对象调用,这让全局作用域有点名不副实。
如果对象需要定义很多方法,那么就要定义很多个全局函数,于是我们这个自定义的引用类型就丝毫没有封装性可言了。


employee3和employee4的work属性都指向全局作用域下的work函数对象。

让我们来看看Employee实例对象调用全局作用域下的work函数时,work函数里this的指向:





regular function形式调用window对象的work时,全局作用域下的work函数中的this指向window对象:



5.3 原型模式