引用类型之「对象/数组」

2020-01-23  本文已影响0人  果汁凉茶丶

# 引用类型种类

JS中引用类型有:
(1) 对象:Object
(2)数组:Array
(3)日期:Date
(4)正则:RegExp
(5)函数:Function
(6)基本包装类型:String、Number、Boolean
(7)单体内置对象:Global、Math

# 概念澄清

Object引用类型,简称Object类型(或对象类型),是引用类型中最使用最广泛的一种数据结构,常说的对象,是Object类型的实例,使用new关键字后面跟构造函数 Object来创建,构造函数本身只是一个函数,只不过该函数是出于创建新对象的目的而定义的。
  判断一个实例的数据类型是否是引用类型,使用typeof方法,判断结果是否是字符串object;而判断一个实例的数据类型是哪一种引用类型,可以使用constructor 获取构造函数,或者使用instanceof操作符判断构造函数的原型对象是否在实例的原型链上。
  阅读 构造函数与原型(链)

# Object 类型

  Object类型是ECMAScript中使用最多的数据类型,对象是Object类型的实例,主要应用在应用程序中存储和传输数据中,用来封装参数传递。

创建Object实例
(1)new操作符 + 构造函数:var person = new Object(); person.name = 'zhangfs'
(2)对象字面量表示法:var person = { name: 'zhangfs' } 属于(1)的简写形式。但它不会调用Object构造函数。

访问属性
(1)点表示法:var userName = person.name 最常用。
(2)方括号表示法:var userName = person['name'] 这种方式可支持变量访问属性,可以访问数值属性,及属性中包含空格等特殊情况。
【注】如果对象中不存在该值,不会报错,而是返回 undefined

删除属性
(1)使用delete关键字:delete person.name
【注】如果只是想清空属性,使用person.name = ''即可

复制(拷贝)
(1)浅拷贝:其原理是将源对象的每一个属性都做一个拷贝,并不考虑属性的类型是否为引用类型。扩展一些理解,其只是做了地址引用的复制,并没有得到真正的值。经过浅拷贝的变量修改属性仍有可能对源对象造成影响

function shallowCopy(obj) {
  let c = {}
  for (let x of obj) {
    c[i] = x
  }
  return c
}
# Object.assign()实现的浅拷贝
var obj = {a: 1, b: { b1: 2 }}
var o = Object.assign({}, obj)
o.a = 3
o.b.b1 = 4
console.log(obj)
// --- output ---
// { a: 1, b: { b1: 3 } }

(2)深拷贝
  顾名思义:就是对任意层次的属性值均做拷贝,拷贝后数据不再互相干扰
1)JSON序列化深拷贝:JSON.parse(JSON.stringify(obj))
【注】这种深拷贝方法缺陷在于:JSON.stringify()会主动丢弃值为undefinedFunctionSymbol原型属性。所以parse()后的值可能与源数据有缺失。
2)递归式深拷贝
  简化版的递归式深拷贝,重点关注对象属性的attr.constructor === Array / Object来进行判断是否递归即可,本文我们写的更加全面一些,包含了对其他引用类型的判断

function deepClone(obj) {
  if (obj === null) return null // typeof null === 'object'
  if (typeof obj !== 'object') return obj
  // typeof === 'object' 的情形
  var newObj = obj.constructor() // 保持继承链
  for (var key in obj) {
    // 数组是下标,对象是key,hasOwnProperty都能取到,而Date,RegExp,Function取不到。且不考虑原型属性
    if (newObj.hasOwnProperty(key)) {
      // 使用arguments.callee将函数名解耦
      newObj[key] = typeof obj[key] === 'object' ? arguments.callee(obj[key]) : obj[key]
    } else {
      newObj[key] = obj[key]
    }
  }
  return newObj
}

遍历
1)for-in:返回能通过对象访问的、可枚举的属性。包括实例属性和原型属性
2)Object.keys(obj):返回对象的实例属性,不包含原型属性。如果属性是数字或可转成数字类型的字符串,会默认按数字从小到大排序输出。
3)Object.getOwnPropertyNames(obj):返回对象自身所有实例属性和原型属性

类型判断
(1)实例 instanceof 构造函数instanceof操作符用来判断构造函数的原型对象是否在实例的原型链上
(2)实例.constructor === 构造函数:执行此行代码实质上是在执行实例.__proto__.constructor === 构造函数 是否成立
(3)typeof 实例:只能用来判断是否是引用类型,不能判断是否是对象。


# Array 类型

Array类型也是ECMAScript中最常使用的数据类型之一,数组是Array类型的实例,ECMAScript数组类型与其他语言的数组有较大差别。它支持每一项存储任何类型的数据,并且数组长度可以动态调整,该长度会随着数据的添加自动增长来容纳新增数据。

创建
(1)new后面跟构造函数:var colors = new Array();
(2)字面量表示法:var color = []
  (1)方法中,如果预先传入了长度new Array(3),则,即使未给任意一项赋值,colors.length 的值也会为3。除非手动修改长度或执行了影响长度的逻辑,如赋值了第四项。初始化时这三项值都是undefined
  另外,构造函数方式也可以省略new操作符,也支持传入预先设定好的数组项。如var colors = Array('red', 'blue')

读取和设置
(1)读取:var c0 = colors[0]
  如果下标小于length,返回下标对应项的值。大于等于length返回undefined
(2)设置:color[1] = 'pink'
  如果下标小于length,覆盖下标对应项的值。大于等于lengthlength扩容至该下标加1,并将该值赋值给该下标。这是由ECMAScript数组长度可变特性所提供的。

数组的length属性可以用来在数组末位一处或增加新项。

检测数组类型 (经典问题)
(1)value instanceof Array:该操作符的MDN描述如下

The instanceof operator tests the presence of constructor.prototype in object's prototype chain

即:检查value的原型链中是否存在Array构造函数的的原型对象,对于Object或Array而言,原型链上只有一环,instanceof操作符相当于判断如下关系是否成立:

value.__proto__ === Array.prototype

(2)Array.isArray(value):ES6语法,isArray()是构造函数Array的实例方法,能解决跨全局执行环境问题。

常见转换方法
(1)toString():返回数组中每个值的字符串形式拼接而成的一个以逗号分隔的字符串。
  该方法有个经典问题作为拓展如下:

var a = {
  i: 1,
  toString: function() {
    return a.i++
  }
}

if (arr == 1 && arr == 2 && arr == 3) {
  console.log(true)
} else {
  console.log(false)
}
// true

结果很意外的输出了true。看起来有点莫名其妙,其实在执行arr == 时候,会默认执行它的a.toString()方法,而我们重写了该方法,第一次执行后,由于 a.i++特性先返回了1再执行加法,变成2,第二次第三次执行类似。
  该案例主要是考察读者对 arr == 的执行原理的理解以及toString()相对于数据结构 a 的存在关系。

(2)valueOf():返回该数组
(3)toLocaleString():转换成字符串。他与toString()唯一区别在于,它为数组的每一项执行的是toLocaleString()方法而非toString()方法。开发者可以重写toLocaleString()方法来实现特殊的结果。
(4)join():拼接。默认的toString()方法返回的是以逗号分隔的字符串,join()方法允许自定义分隔的内容如空格,斜杆等。当没有参数时,默认为逗号。

栈方法
  栈是一种后进先出LIFO的数据结构,每一项的推入和弹出都是发生在栈的顶部

var color = ['red', 'blue']
var count= color.push('green', 'pink')
console.log(count); // 4
console.log(color); // ['red', 'blue', 'green', 'pink']

var item = color.pop();
console.log(item); // 'pink'

队列方法
  队列方法是先进先出FIFO的数据结构,队列在列表的末尾添加项,在前端移除项。

重排序方法

function compare(A, B) {
  A === B ? 0 : A > B ? 1 : -1
}
// 如果A、B是数字类型,可简化为
funtion compare(A, B) {
  return A - B
}

操作方法

var A = [1, 2, 3]
var B = c.concat(4, [5, 6], {a: 7, b: 8})
console.log(B)  // [1, 2, 3, 4, 5, 6, {a: 7, b: 8}]
console.log(A)  // [1, 2, 3]

位置方法

遍历迭代方法

  以上五个方法前三个比较好理解,map()filter()比较容易混淆用法,filter()是针对数组的项本身进行过滤,返回的项数通常都小于数组自身。而map()是针对项中的部分属性进行过滤,返回的项数通常等于自身。

var bookList = [
  { id: 100, name: '语文', price: 25 },
  { id: 101, name: '数学', price: 30 },
  { id: 102, name: '英语', price: 35 }
]
booklist.map(book => book.id); // [100, 101, 102]
booklist.filter(book => book.id > 101); // [{ id: 102, name: '英语', price: 35 }]

归并方法

var arr = [1, 2, 3, 4]
var sum = arr.reduce((prev, cur, index, arr) => {
  return prev + cur 
})
console.log(sum); // 10
上一篇 下一篇

猜你喜欢

热点阅读