zoukankan      html  css  js  c++  java
  • 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()
  • 相关阅读:
    特征选择(1)
    sklearn.preprocessing.OneHotEncoder
    朴素贝叶斯算法
    机器学习中 生成式模型 VS 判别式模型
    PHP-FPM 多进程模型
    PHP动态模式和静态模式区别
    Nginx的异步非阻塞
    php并发控制 , 乐观锁
    什么是乐观锁,什么是悲观锁
    redis集群和哨兵的区别
  • 原文地址:https://www.cnblogs.com/zhurong/p/14464930.html
Copyright © 2011-2022 走看看