JavaScript 基础(五):Object

基础系列文章:

JavaScript 基础(一):null 和 undefined

JavaScript 基础(二):String

JavaScript 基础(三):Number

JavaScript 基础(四):Array

JavaScript 基础(五):Object

JavaScript 基础(六):Map、Set

 

在 JS 中 Object 是一个很重要的数据类型。并且在实际开发中使用频率很高。

但是 Object 也是难点,特别是:原型、原型链、继承、Function 等。

下面就从 Object 的各个方面进行介绍。

一、Property(属性)

Object 是一组数据和功能的集合。创建完成后添加属性和方法(property)。

这些 property 在对象里面是以键值对的形式存储的。

Object 本身也有一些属性,叫属性描述符。

1、数据属性

2、访问器属性

上面两个的具体有:

/**
 * 可以通过它对定义的属性有更大的控制权,主要有下面几个
 * value ---- 获取属性时返回的值
 * writable ---- 该属性是否可写
 * enumerable ---- 该属性在 fon in 循环中是否会被枚举
 * Configurable ---- 该属性是否可被删除
 * set() ---- 属性的更新操作
 * get() ---- 获取属性操作
 *
 * 分别对应:
 * 数据属性:value、writable、enumerable、configurable
 * 访问器属性:get()、set()、enumerable、configurable
 */

下面是具体的代码展示:

let person = {}
// 直接赋值,是数据属性
person.legs = 2 // 这种方式添加的属性,其他描述符都是 true
console.log('person descriptors:', Object.getOwnPropertyDescriptors(person))  // legs: { value: 2, writable: true, enumerable: true, configurable: true }

// 数据属性形式,这种方式 除了 value 默认 undefined,其他都是默认 false
Object.defineProperty(person, 'legs1', {
  value: 2,
  writable: true,
  enumerable: true,
  configurable: true
})

// 访问器属性形式
Object.defineProperty(person, 'legs2', {
  set: function(v) {
    return (this.value = v)
  },
  get: function() {
    return this.value
  },
  enumerable: true,
  configurable: true
})
person.legs2 = 5

在 Vue2.x 中使用的数据拦截就是用的“访问器属性”,在 get、set 里面进行监听操作,进而实现数据动态绑定。

二、Object 属性方法

我们经常用到的 Object 就是我们需要研究的第一个对象。

可以使用 Object 自身的一些方法来查看自身。

console.log(Object.getOwnPropertyDescriptors(Object)) // 查看 Object 自身的全部属性及详情
console.log(Object.getOwnPropertyNames(Object)) // 查看 Object 全部的属性名称列表

属性名称列表打印出来是:

[
  'length',
  'name',
  'prototype',
  'assign',
  'getOwnPropertyDescriptor',
  'getOwnPropertyDescriptors',
  'getOwnPropertyNames',
  'getOwnPropertySymbols',
  'is',
  'preventExtensions',
  'seal',
  'create',
  'defineProperties',
  'defineProperty',
  'freeze',
  'getPrototypeOf',
  'setPrototypeOf',
  'isExtensible',
  'isFrozen',
  'isSealed',
  'keys',
  'entries',
  'fromEntries',
  'values'
]

属性详情内容比较多,这里就只展示几个,并且 Object 的属性都是用数值属性定义的:

{
  length: { value: 1, writable: false, enumerable: false, configurable: true },
  name: {
    value: 'Object',
    writable: false,
    enumerable: false,
    configurable: true
  },
  prototype: {
    value: {},
    writable: false,
    enumerable: false,
    configurable: false
  },
  assign: {
    value: [Function: assign],
    writable: true,
    enumerable: false,
    configurable: true
  },
  getOwnPropertyDescriptor: {
    value: [Function: getOwnPropertyDescriptor],
    writable: true,
    enumerable: false,
    configurable: true
  }
}

1、属性相关

属性相关的较多,下面就列举出来。

1)、获取属性信息

/**
 * Object.getOwnPropertyDescriptor(obj,property)
 * 查看一个对象的某一个属性的详情,也可以一窥内置的对象属性
 */

/**
 * Object.getOwnPropertyDescriptors(obj)
 * 用于获取对象所有属性的描述
 */


/**
 * Object.getOwnPropertyNames(obj)
 * 返回当前对象的所有属性名,包括不可枚举的
 */


/**
 * Object.keys(obj)
 * 返回的是自身可枚举的
 */

/**
 * Object.values(obj)
 * 用于获取对象自身所有可枚举属性的值
 * 顺序和 for in 顺序一致(不同点是,for in 包括继承的属性)
 */

/**
 * Object.entries(obj)
 * 返回的是对象自身的可枚举数据键值对(可迭代)
 * 顺序和 for in 顺序一致(不同点是,for in 包括继承的属性)
 */

/**
 * Object.getOwnPropertySymbols(obj)
 * 返回当前对象的 Symbol 属性,一个对象初始化后不包含 Symbol 属性
 * 只有当赋值了 Symbol 属性才可以取到值
 */

2)、设置、赋值属性

/**
 * Object.assign(target,...sources)
 * 该方法用于将源对象(sources)的可枚举属性赋值到目标对象(target),可以同时将多个源对象复制给目标对象
 * 有同名属性,复制后后面的会覆盖前面的属性
 * 只复制自身的可枚举的属性
 * 属性类型是 Symbol 的也可以复制
 * 对于嵌套类型的对象,是直接替换
 * 对于数组,取 length 最大值,并后面覆盖前面
 */
let target = { a: 1 }
let source1 = { b: 2 }
let source2 = { c: 3 }
let objAssign = Object.assign(target, source1, source2)
console.log('target:', target)
console.log('objAssign:', objAssign)
console.log(Object.assign([1, 2], [3, 4, 5], [6, 7]))
// 打印结果
// target: { a: 1, b: 2, c: 3 }
// objAssign: { a: 1, b: 2, c: 3 }
// [ 6, 7, 5 ]

/**
 * Object.defineProperty(obj,prop,descriptor)
 * 用于定义对象的属性
 *
 * Object.defineProperties(obj,props)
 * 基本功能同上面,但是可以一次定义多个属性
 */
let glass = Object.defineProperties(
  {},
  {
    color: {
      value: 'transparent',
      writable: true
    },
    fullness: {
      value: 'half',
      writable: true
    }
  }
)

2、创建对象

Object 属性中有两个方法可以创建新对象。

/**
 * Object.create(obj,descr)
 * 创建一个新对象,并设置原型,用属性描述符定义对象的原型属性
 *  obj:创建新对象的原型
 *  descr:新对象的属性,以对象形式
 */
let person1 = { hi: 'hello' }
Object.defineProperty(person1, 'name', {
  value: 'ZHT',
  enumerable: true
})
// 新创建的 child 的原型是 person1
let child = Object.create(person1, {
  prop: {
    value: 1
  },
  childName: {
    value: 'ZHT-C',
    enumerable: true,
    writable: true
  }
})


/**
 * Objec.fromEntries(iterable)
 * 创建一个以给定数组、map或者其他可迭代为属性的新对象
 * 是 entries 的逆操作
 */

 let arr = [['cow','牛牛111'],['pig','佩奇']]
 let objFromEntries = Object.fromEntries(arr)
 console.log('objFromEntries:',objFromEntries)
// 打印结果:objFromEntries: { cow: '牛牛111', pig: '佩奇' }

3、原型相关

与原型相关就是设置、获取原型

/**
 * Object.prototype
 * Object 原型,是一个对象
 */

/**
 * Object.getPrototypeOf(obj)
 * 获取当前对象的原型
 */

/**
 * Object.setPrototypeOf(obj,prototype)
 * 设置该对象(obj)的原型为 prototype
 */

4、其他 Object 属性方法

/**
 * Object.preventExtensions(obj)
 * Object.isExtensible(obj)
 * preventExtensions 用于禁止向对象添加更多属性(不可扩展对象,但是可在其原型中添加)不可逆操作
 * isExtensible 检查对象是否可以添加对象
 */

/**
 * Object.seal(obj)
 * Object.isSealed(obj)
 * seal 用于密封一个对象,并返回密封后的对象。这个对象的属性不可配置:这种情况下,只能变更现有属性的值,
 *  不能删除或者重新配置属性的值
 * isSeal 判断是否是密封的
 * 密封对象 不可扩展 不可逆操作
 */

/**
 * Object.freeze(obj)
 * Object.isFrozen(obj)
 * freeze 用于冻结一个对象,冻结后:不可添加属性、删除属性、不能修改属性值,以及配置值 不可逆操作
 * isFrozen 用于判断是否被冻结
 */

/**
 * 不可扩展、密封、冻结 对比
 */
// 方法名                        增(extensible)      删(configurable)      改(writable)
// Object.preventExtensions      ×                 √                      √
// Object.seal                  ×                    ×                     √
// Object.freeze                ×                    ×                      ×


/**
 * Object.is(value1,value2)
 * 用于比较两个值是否严格相等 基本等同于(===)
 * 有两个地方不一样:+0 不等于 -0,NaN 等于自身
 */

 

三、Prototype(原型)

在 JS 中所有的对象都有原型。

1、Object 原型

Object 原型是一个对象,其属性有:

/**
 * Object.prototype
 * Object.prototype.constructor ---- 构造器
 * Object.prototype.toString(radix) ---- 返回描述对象字符串,radix 是当为 Number 时进制
 * Object.prototype.toLocaleString() ---- 基本同上
 * Object.prototype.valueOf() ---- 返回基本类型的 this 值
 * Object.prototype.hasOwnProperty(prop) ---- 对象中是否有该属性(原型链上的返回 false)
 * Object.prototype.isPrototypeOf(obj) ---- 当前对象的原型是不是目标对象(括号中的是当前对象)
 * Object.prototype.propertyIsEnumerable(prop) ---- prop 是否是当前对象 for in 中的值
 */

2、新建对象原型

新建的对象的原型遵循下面的方法获取(没有 prototype):

1)、对于 obj.__proto__ 这个属性,可以读写 obj 的原型,但是这个属性不是在 ES6
可见这个是内部的属性,不是正式对外的 API
2)、所以操作 obj 的原型最好的方法是:Object.getPrototypeOf()、Object.setPrototypeOf()、Object.create()

3)、都是调用的 Object.prototype.__proto__,在 ES6 中推荐的是 setPrototypeOf

四、增强语法

1、计算属性

对象的属性名称可以根据动态生成

 const nameKey = 'name'
 const ageKey = 'age'
 const jobKey = 'job'
 let uniqueToken = 0

 function getUniqueKey(key){
   return `${key}_${uniqueToken++}`
 }

 let person = {
   [getUniqueKey(nameKey)]:'ZHT',
   [getUniqueKey(ageKey)]:29,
   [getUniqueKey(jobKey)]:'code'
 }

 console.log(person)  // { name_0: 'ZHT', age_1: 29, job_2: 'code' }

2、对象解构

解构是ES6的新语法,在使用中可适用的场景很多,这里就简单举例,详细看阮老师的变量的解构赋值

let person1 = {
  name:'ZHT',
  age:29
}

let {name,age} = person1    // 可以和对象属性同名
let {name:personName,age:personAge} = person1    // 也可以设置别名

五、对象创建

1、对象字面量

对象字面量创建对象,语法上更加的简洁。形式如下:

let obj1 = {
  name: 'obj1',
  value: 123
}

// 可以继续向对象中添加属性
obj1.age = 12

// 此时新建的对象的原型为空对象,可以显示设置原型
Object.setPrototypeOf(obj1,prototype)

2、new Object

此种方法创建的对象和上面一样,只是写法上面。

同样的,新建的对象原型也是空对象。

3、Object.create

create 方法创建的对象,和上面两种的区别是可以直接指定原型

具体的使用上面有介绍。

 

下面是几种批量创建对象的模式

4、工厂模式

工厂模式是软件设计中比较常见的设计模式。用抽象创建特定对象的过程。

function createPerson(name,age,job){
  let obj = {}
  obj.name = name
  obj.age = age
  obj.job = job
  obj.sayName = function(){
    console.log(this.name)
  }

  return obj
}

let person1 = createPerson('s',10,'stu')

上面的例子就是把创建 person 对象抽象出来,可以直接调用创建新对象。对于大量、重复创建可以提供效率。

但是工厂模式存在问题,不能解决对象标识问题(即新创建的对象是什么类型)

5、构造函数模式

在 ES 中构造函数是用于创建特定类型对象的。像内置的 Object、Array 都是构造函数,可以在执行环境中直接使用。

同时我们也可以自定义构造函数,以函数的形式给自己的对象类型定义属性和方法。

下面是构造函数模式例子:

function PersonConstructor(name,age,job){
  this.name = name
  this.age = age
  this.job = job
  this.sayName = function(){
    console.log(this.name)
  }
}

const person1 = new PersonConstructor('jike',40,'CEO')
const person2 = new PersonConstructor('Robot',39,'CFO')

console.log(person1.constructor === PersonCon)    // true
console.log(person1 instanceof Object)    // true
console.log(person2 instanceof PersonCon)    // true

首先构造函数模式和工厂函数的区别

1)、没有显示的创建对象

2)、属性和方法直接赋值给了 this

3)、没有 return

看了这些你有没有疑问呢?那为什么能够创建并返回一个新对象呢?

这里就要说到 ES 里面 new + 构造函数 的语法糖的具体内部操作了:

a)、在内存中创建一个新对象

b)、这个新对象的[[prototype]]特性被赋值为构造函数的 prototype 属性(即 __proto__,建议用 Object.getPrototypeOf 取值)

c)、构造函数内部的 this 被赋值为这个新对象(this 指向新对象)

d)、执行构造函数内部代码

e)、如果构造函数有返回非空对象,返回这个对象;如果没有 返回 this(新创建的对象)

所以上面的疑问也就得到了解决。

同时也解决了工厂模式的问题——对象标识。

上面的代码中看出,新建对象的构造函数指向创建它的构造函数

而且用 instanceof 构造函数、Object 都是 true。这两个都在原型链上。

对于构造函数几点说明

A)、构造函数也是函数,只是执行上面有点不一样,如果不加 new 和普通函数一样

B)、构造函数当普通函数时要注意 this 的指向问题

C)、存在问题:构造函数中如果有方法的话,创建的每个实例都创建一遍,而且做的事情都一样,但是又有同名不相等的问题

   虽然可以放在外面定义,直接指向外面,这样会污染全局,并且方法多了也有问题。

console.log(person1.sayName === person2.sayName)    // false

6、原型模式

由于构造函数模式存在一定的问题,所以就有了原型模式。(在这里我们只讲原型模式继承,对原型、原型链暂不做讨论)

function Person(){}
// 直接在原型上面添加属性、方法
Person.prototype.name = 'ZHT'
Person.prototype.age = 24

console.log(Person.toString())

// 自定义构造函数时,原型对象只会自动获得 constructor 属性,其他的方法继承自 Object
//  1、这个 constructor 指回与之关联的构造函数
console.log(Person.prototype.constructor === Person)    // true

// 根据这个可以一直向上查找,最终源于 Object 原型
console.log(Person.prototype.__proto__ === Object.prototype)    // true
console.log(Person.prototype.__proto__.constructor === Object)    // true
console.log(Person.prototype.__proto__.__proto__ === null)    // true

// 调用构造函数创建一个实例时,实例内部的 [[prototype]] 指向构造函数原型
//  1、在一般实现中 [[prototype]] 是以 __proto__ 暴露出来的
//  2、实例和构造函数原型有直接联系
let personPro = new Person()
console.log(personPro.__proto__ === Person.prototype)   // true

原型模式也有自己的缺点:

所有的属性、方法都是在原型上面,通过这种方式创建的对象实例共享这些方法。对于有些时候需要相对独立的,原型就有问题了。

7、混合模式

上面的两种:构造函数模式、原型模式 做的都太绝对,相当于两个极端。所以在实际的使用中还是两者的结合。

// 构造函数模式
function Animation(obj){
  // 根据传入初始化
  this.name = obj.name
  this.id = obj.id
}

// 原型模式
Animation.prototype = {
  // 做相同的事情
  eat:function () {
    console.log(this.name,',开始吃饭了……')
  }
}

let dog = new Animation({name:'dog',id:1})
dog.eat()

 

posted @ 2021-03-05 10:48  漠里  阅读(319)  评论(0编辑  收藏  举报