阅读 101

JavaScript系列 -- Object 详解

前言

在 JavaScript 中,万物皆为对象,函数其实也是对象,数组其实也是另一种形式的对象,就连 ES6 新增的数据结构 Set、Map也其实都是对象中的一种

Object 里面其实有很多方法是很实用的,值得我们去研究

而且,所有的“对象”的原型链的末端都是 Object.prototype 对象,也就是说它们都可以访问到这个对象的属性/方法,所以研究 Object 和 Object.prototype 的属性/方法都是非常有必要的

我们利用 Object.getOwnPropertyNames() 方法查看 Object 和 Object.prototype 都有哪些属性/方法:

image.png

Object 的属性的特性

数据属性

它包含的是一个数据值的位置,在这可以对数据值进行读写

  • var obj = { property: ... } 方法 / obj.property = ... 方法创建对象属性的默认值
configurable: true,  // 1. 能通过delete删除属性从而重新定义属性;2. 能修改属性的特性
enumerable: true,    // 属性可通过 for...in 枚举到
writable: true,      // 属性的值可修改
value: ... // 属性的值
复制代码
  • Object.defineProperty() 方法创建对象属性的默认值
configurable: false, // 1. 不能通过delete删除属性从而重新定义属性;2. 不能修改属性的特性
enumerable: false,   // 属性不可通过 for...in 枚举到
writable: false,     // 属性的值不可修改
value: ... // 属性的值
复制代码

image.png

访问器属性

configurable: true(默认值)// 1. 表示能否通过delete删除属性从而重新定义属性;2. 表示能否修改属性的特性
enumerable: true(默认值) // 表示能否通过for-in循环返回属性
get: undefined(默认值) // 在读取属性时调用的函数
set: undefined(默认值) // 在写入属性时调用的函数
复制代码

值得注意的是:访问器属性不能直接定义,要通过 Object.defineProperty() 这个方法来定义

var obj = {
    age: 18
}
Object.defineProperty(obj,"say",{ // 注意:这里必须是新方法
    // 定义 getter
    get: function() {
        return this.age // 注意需要用 this 指向 obj 对象,否则会认为 age 是 window.age
    },
    // 定义 setter
    set: function(value) {
        if( this.age !== value ){
            this.age = value
        }
    }
})
复制代码

控制台输出 obj 对象:

image.png 测试其 say 属性的 set() 和 get() 方法如下:

image.png

Object 的方法

下面的方法的参数解读:

  • obj:表示创建出来的实例对象
  • property:表示某个属性的名称,注意是字符串形式(也就是说要加" ")
  • props:表示包含了属性的属性的一个对象,如:
{
    configurable: true,  // 1. 能通过delete删除属性从而重新定义属性;2. 能修改属性的特性
    enumerable: true,    // 属性可通过 for...in 枚举到
    writable: true,      // 属性的值可修改
    value: ... // 属性的值
}
复制代码
  • [props]:表示 props 的一个数组集合,格式为:
[
    property1: {
        configurable: true,  // 1. 能通过delete删除属性从而重新定义属性;2. 能修改属性的特性
        enumerable: true,    // 属性可通过 for...in 枚举到
        writable: true,     // 属性的值可修改
        value: ... // 属性的值
    },
    property2: {
        configurable: true,  // 1. 能通过delete删除属性从而重新定义属性;2. 能修改属性的特性
        enumerable: true,    // 属性可通过 for...in 枚举到
        writable: true,      // 属性的值可修改
        value: ... // 属性的值
    },
    ...
]
复制代码

Object.defineProperty( obj , property , props)

两个功能:

  • 新增属性,并设置其特性
  • 对已有属性设置特性(前提是该属性的 configurable 值是 true 才可以,否则会报错)
var obj = {}
Object.defineProperty( obj , "name" , {
    configurable: true,  // 1. 能通过delete删除属性从而重新定义属性;2. 能修改属性的特性
    enumerable: true,    // 属性可通过 for...in 枚举到
    writable: false,      // 属性的值不可修改
    value: "Jack" // 属性的值
})
console.log(obj) // {name: "Jack"}
obj.name = "Deck"
复制代码

控制台输出,可以看到该属性的值没有被改变,但使用 Object.defineProperty() 方法即可改变其值

image.png image.png

Object.defineProperties( obj , [props])

功能见 Object.defineProperty() 方法,只不过是可以同时设置多个属性的特性

var obj = {};
Object.defineProperties(obj, {
  'name': {
    configurable: true,
    enumerable: true,
    value: "Jack",
    writable: true
  },
  'age': {
    configurable: true,
    enumerable: false,
    value: 18,
    writable: true
  }
})
console.log(obj)
for(let i in obj){
    console.log(i)
}
复制代码

控制台可以把 name 属性和 age 属性都输出出来,但使用 for...in 循环遍历不到 age 属性

image.png

Object.create( __proto__ , [props] )

功能:

  • 新建一个对象,传入第一个参数作为该对象的原型对象
  • 第二个参数可选,可用于同时创建该对象本身的属性
const person = {
  isHuman: false,
  printIntroduction: function() {
    console.log(`My name is ${this.name}. Am I human? ${this.isHuman}`);
  }
};

const me = Object.create(person);
复制代码

image.png

  • 注意:使用 . 创建新属性,该属性是归属于该对象的本身的属性
me.name = 'Matthew';
复制代码

image.png

  • 注意:如果本身属性和原型的属性重名,以本身属性的值为准
me.isHuman = true; // inherited properties can be overwritten
me.printIntroduction();
复制代码

image.png

这就意味着,虽然说每个对象会访问到的原型链上任何一个对象的属性/方法,但使用属性/方法时要是同名是不会干扰到的

Object.assign( { } , obj ) 、Object.assign( [ ] , arr )

功能:

  • 用于拷贝 对象 / 数组
  • 第一层为深拷贝,第二层及以上为浅拷贝

第一层是深拷贝:

var arr1 = [1,2,3]
var arr2 = Object.assign([],arr1)
复制代码

image.png 第二层(指的是【对象属性/ 数组元素】是【对象 / 数组】)为浅拷贝

var obj1 = {
    "info": {
        "name": "Jack",
        age: 18
    },
    gender: 1
}
var obj2 = Object.assign({},obj1)
复制代码

image.png

Object.getOwnPropertyDescriptor( obj , property )

功能:获取实例对象的指定属性的数据属性

参数:

  • obj: 要定义/修改属性的属性的对象
  • propertyName: 要定义/修改属性的属性的名称

功能:通过Object.getOwnPropertyDescriptor()方法,我们可以查看person对象里的自身属性 name 及其数据属性(注意,这里只是能查到自身属性的,并不能查到原型链上的)

image.png

Object.getOwnPropertyDescriptors(obj)

参数:obj: 要定义/修改属性的属性的对象

功能:和Object.getOwnPropertyDescriptor()方法一样,区别在于是获取到全部属性及其数据属性(包括 自身和原型可枚举和不可枚举 的所有属性)

image.png

Object.defineProperty()是ES5中的新方法,IE9(IE8部分实现,只有dom对象才支持)以下浏览器不支持,一些旧的浏览器可以通过非标准方法Object.defineGetter()和Object.defineSetter()来设置

附加内容:

Vue 实例的 data 选项里面的对象的字段都还有各自的 setter 和 getter 方法:

image.png

直接打印出来会出现 get xxx 和 set xxx 的属性,其值即是 xxx 字段的 get 和 set 方法,用 Object.getOwnPropertyDescriptors 方法打印出来可以知道这些都属于 _ob_ 观察器里的内容

Object.getOwnPropertyNames( obj )

功能:获取实例对象的全部自身属性的名称(包括不可枚举的),以数组形式返回,每个元素均为字符串形式

Object.keys( obj )

功能:获取实例对象的全部自身属性的名称(只包括可枚举的),以数组形式返回,每个元素均为字符串形式

注意:for...in 和 Object.getOwnPropertyNames() 和 Object.keys() 的区别:

共同点:都是获取属性名称,返回数据类型为数组;

不同点:

  • for...in:返回自身和原型中所有可枚举的属性
  • Object.getOwnPropertyNames():可返回自身所有属性,包括可枚举不可枚举的属性
  • Object.keys():只返回自身所有可枚举的属性

image.png

Object.values( obj )

功能:获取实例对象的全部自身可枚举属性的值,以数组形式返回

Object.entries( obj )

功能:获取实例对象的全部自身可枚举属性的名称及其值,以二元数组形式返回,每个元素均为[propertyName,value]

image.png

Object.fromEntries()

功能:是 Object.entries() 的逆方法,把二维数组变成对象

image.png

Object.is( value1 , value2)

功能:用于判断两个值是否为同一个值 Object.is() 方法判断两个值是否为同一个值。

判断规则:如果满足以下条件则两个值相等:

  • 都是 undefined
  • 都是 null
  • 都是 true 或 false
  • 都是相同长度的字符串且相同字符按相同顺序排列
  • 都是相同对象(意味着每个对象有同一个引用)
  • 都是数字且
    • 都是 +0
    • 都是 -0
    • 都是 NaN
    • 或都是非零而且非 NaN 且为同一个值

与 == 运算符、=== 运算符的区别:

  • 与== 运算不同。 == 运算符在判断相等前对两边的变量(如果它们不是同一类型) 进行强制转换 (这种行为的结果会将 "" == false 判断为 true),而 Object.is() 不会强制转换两边的值。
  • 与=== 运算也不相同。 === 运算符 (也包括 == 运算符) 将数字 -0 和 +0 视为相等 ,而将 Number.NaN 与 NaN 视为不相等。而 Object.is() 解决了这两个问题

Object.getPrototypeOf( obj )

功能:返回对象的原型(__proto__对象)

image.png

每次我们新建一个对象:

var obj = {}
var obj = new Object
var obj = new Fun
复制代码

得到的对象的原型(__proto__)都是 Object.prototype,即上面第三行的输出内容

Object.setPrototypeOf( obj , __proto__ )

功能:设置一个指定的对象作为另一个对象的原型

image.png

Object.prototype 的方法

Object.prototype.hasOwnProperty( property )

功能:判断当前对象的某个属性是否为自身属性

Object.prototype.isPrototypeOf( obj )

功能:判断目标对象是否在当前对象的原型链

Object.prototype.propertyIsEnumerable( property )

功能:检测当前对象的某个属性能否被 for...in 或 Object.keys() 遍历

Object.prototype.valueOf()

JavaScript 调用 valueOf 方法将对象转换为原始值。你很少需要自己调用 valueOf 方法;当遇到要预期的原始值的对象时,JavaScript 会自动调用它。

默认情况下,valueOf 方法由 Object 后面的每个对象继承。 每个内置的核心对象都会覆盖此方法以返回适当的值。如果对象没有原始值,则v alueOf 将返回对象本身。

JavaScript的许多内置对象都重写了该函数,以实现更适合自身的功能需要。因此,不同类型对象的valueOf()方法的返回值和返回值类型均可能不同

image.png

Object.prototype.toString( obj )

功能:检测具体的数据类型。结合 call() 方法把要检测类型的数据放在 call() 里面

改变 Object.prototype.toString() 方法里面的 this 指向,规定其指向我们要检测的数据

var Type = function(obj){
    return Object.prototype.toString.call( obj ).slice(8,-1).toLocaleLowerCase()
}
console.log(Type([1,2,1]))
console.log(Type({a: 6}))
console.log(Type(5))
console.log(Type("hhh"))
console.log(Type(undefined))
console.log(Type(null))
console.log(Type(false))
console.log(Type(Symbol))
复制代码

image.png

Object.prototype.toLocaleString()

功能:返回一个该对象的字符串表示。此方法被用于派生对象为了特定语言环境的目的(locale-specific purposes)而重载使用

附加内容

给对象添加函数的四种写法的区别

// 写法1
var person1 = {
    name: "p1",
    sayThis() {
        console.log(this);
    }
};
// 写法2
var person2 = {
    name: "p2",
    sayThis: function() {
        console.log(this);
    }
};
// 写法3
var person3 = {
    name: "p3",
    sayThis:()=> {
        console.log(this);
    }
};
// 写法4
var person4 = {
    name: "p4",
};
person4.sayThis = function(){
    console.log(this)
}
复制代码
  1. 写法1 和 写法2

写法1 和 写法2 没有区别,this 指向一致

  1. 写法1 和 写法3

this 指向的区别:

person1.sayThis(); // person1 对象
person3.sayThis(); // window 对象
复制代码

image.png

  1. 写法2 和 写法4
  • 从意义上理解:

    • 写法2 相当于在创建一个对象的同时创建其中的函数方法
    • 写法4 相当于创建对象之后再给它继续添加函数
  • 从 bug 的角度上看

    • 写法2 会重置对象
    • 写法4 会保留对象之前的属性和方法

参考文章

文章分类
前端
文章标签