深入理解JS中的对象

1. 对象是什么

对象就键/值对的集合,通过.属性访问或['...']键访问,访问实际是调用内部默认的[[Get]]操作,如果对象内没有找到,还会查找原型链。

2. 如何创建对象(语法)

定义对象的方法有两种:

  • 声明文字形式
    var obj = {
        name: 'z'
    }
复制代码
  • 构造形式
    var obj = new Object()
    obj.name = 'z'
复制代码

通常我们都是使用声明文字形式定义对象

3. 数据基本类型,对象的子类型

js中有6中主要类型:string、number、boolean、null、undefined、object

前5中都是简单基本类型,本身并不是对象,这里介绍一下null为什么不是对象

    typeof null == object // true
复制代码

不同对象在底层都表现为二进制,js中二进制前3位都是0的话会被判断为object类型,null的二进制表示全都是0,,所以会有上面的bug。

js中的内置对象

  • String
  • Number
  • Boolean
  • Object
  • Function
  • Array
  • Date
  • Error
  • RegExp

我们听过一种说法,js中一切皆对象,那看下面的例子

var str = 'not object'
console.log(str.length) // 10
复制代码

既然string类型的值不是对象,为什么可以直接在字符串字面量上访问属性和方法。 原因是引擎自动把字面量转化成String对象。

4. 内容

对象的内容是由一些存储在特定命名位置的任意类型的组成的,称之为属性

对象的属性名永远是字符串,

  1. 可计算属性名

   可以在文字形式中使用[]包裹一个表达式当做属性名

var prefix = 'attributes'

var obj = {
    [prefix + 'my']: 1,
    [prefix + 'you']: 2
}

obj.attribitusmy        // 1
obj['attributesyou']    // 2
复制代码
  1. 属性与方法

   通常把属于某个对象(类)的函数叫做“方法”,实际上函数和对象之间只是一种引用的间接关系。

function foo () {
    console.log('foo')
}

var bar = foo   // 对foo变量的引用

var obj = {
    bar: foo
}

obj.bar    // function foo() {...}
bar    // function foo() {...}
复制代码

obj.bar和bar都是对同一个函数的不同引用,不能说这个函数是特别地属于某一个对象。最保险的说法是“函数”和“方法”在js中是可以互换的。

  1. 数组

数组也是对象,虽然每个下标都是整数,但还是可以给数组增加属性

var arr = [1,2,3]
arr.attr = '新属性哦'
arr.length  // 3
arr.attr    // '新属性哦'
复制代码

虽然可以给数组增加属性,但这个属性的属性描述符enumerabe: false,是不可枚举的

  1. 复制对象

复制对象远比我们想象得复杂,因为我们无法选择一个默认的复制算法。 这也是面试中常问的如何浅拷贝和深拷贝一个对象。

对于基本类型的值,它的值就是存储在栈之中的,所以赋值操作就是直接把值复制过去,而对于引用类型的值来说,栈之中只是存储了一个指针,这个指针指向堆中真正存放的值。

对于引用类型的值,浅拷贝就是只需要拷贝值的指针,像赋值操作和ES6的Object.assign(target, source, source, ...)

而深拷贝就是需要拷贝出一个和原值一毛一样的值,但却是两个互不影响的值。

因为JSON.stringify 在序列化的时候会丢失属性 Map, Set, RegExp, Date, ArrayBuffer 和其他内置类型,当需要拷贝的对象中没有这些类型的值时,我们可以

var obj = {...}
var copy = JSON.parse(JSON.stringify(obj))
复制代码

如果有这些类型的值的话,就需要用到递归了

    //使用递归的方式实现数组、对象的深拷贝
    function deepClone(obj) {
        //判断要进行深拷贝的是数组还是对象
        var objClone = Array.isArray(obj) ? [] : {};
        //进行深拷贝的不能为空,并且是对象或者是数组
        if (obj && typeof obj === "object") {
            for (key in obj) {
                if (obj.hasOwnProperty(key)) {
                    if (obj[key] && typeof obj[key] === "object") {
                        // 递归进行拷贝
                        objClone[key] = deepClone(obj[key]);
                    } else {
                        objClone[key] = obj[key];
                    }
                }
            }
        }
        return objClone;
    }
复制代码
  1. 属性描述符
var obj = {}

Object.defineProperty(obj, "a", {
    value: 1,           // 属性值
    writable: true,     // 是否可以修改属性值
    configurable: true, // 属性是否可配置
    enumerable: true    // 是否可枚举
})
// Object.getOwnPropertyDescriptor() 方法返回指定对象上一个自有属性对应的属性描述符
Object.getOwnPropertyDescriptor(obj, "a")
//{
//    value: 1,           // 属性值
//    writable: true,     // 是否可以修改属性值
//    configurable: true, // 属性是否可配置
//    enumerable: true    // 是否可枚举
//}
复制代码

注意:当configurable: false时,还是可以单向的将writable由true改为false

5. [[Get]]、[[Put]]

   对象默认的[[Get]]和[[Put]]操作,分别可以控制属性值的获取和设置。

   在ES5中可以使用getter和setter改写单个属性的默认操作,getter和setter都是隐藏函数,getter在获取值时调用,setter在设置值时使用。

   当给一个属性定义getter和setter时,这个属性被定义为“访问描述符”

var obj = {
    // 给a属性定义一个getter
    get a() {
        return this._a_
    },
    // 给a定义一个setter
    set a(val) {
        return this._a_ = val * 2
    }
}

Object.defineProperty(obj, "b", {
    get: function() {
        return this.a * 2
    },
    enumerable: true
})

obj.a = 1
obj.a   // 2
obj.b   // 4
复制代码

6. 存在性

var obj = {
    a: 2
}

Object.defineProperty(obj, "c", {
    value: 'c',
    enumerable: false,
    configurable: true,
    writable: true
})

("a" in obj)    // true
("b" in obj)    // false

obj.hasOwnProperty("a") // true
obj.hasOwnProperty("b") // false
Object.keys(obj)    // ["a"]
Object.getOwnPropertyNames(obj) // ["a", "c"]
复制代码
  • in操作符会检查属性是否在对象及其[[Prototype]]链中
  • obj.hasOwnProperty()只会检查属性是否在当前对象中,不会检查原型链
  • Object.keys(obj)返回一个数组,包含所有可枚举的属性
  • Object.getOwnPropertyNames(obj)返回一个数组,只查找对象直接包含的属性,包括不可枚举的属性

7. 遍历

  • for.. in 循环可以用来遍历对象的可枚举属性,包括[[Prototype]]链
  • forEach(..)会遍历数组中所有值并忽略回调函数的返回值
  • every(..)会运行到回调函数返回false
  • some(..)会一直运行到回调函数返回true
  • for..of循环首先会向被访问对象请求一个迭代器对象,通过调用迭代器对象的next()方法遍历所有返回值
var arr = [1,2,3]
var it = arr[Symbol.iterator]()
it.next();  // {value: 1, done: false}
it.next();  // {value: 2, done: false}
it.next();  // {value: 3, done: false}
it.next();  // {value: undefined, done: true}
复制代码

转载于:https://juejin.im/post/5cee988f5188251c1929d97e

猜你喜欢

转载自blog.csdn.net/weixin_34025151/article/details/91477684