美文网首页你不知道的JavaScript让前端飞程序员
【你不知道的JavaScript】(六)对象

【你不知道的JavaScript】(六)对象

作者: 虹猫1992 | 来源:发表于2019-01-08 20:25 被阅读2次

(一)语法

对象可以通过两种形式定义:

1. 声明(文字)形式

// 常用
var myObj = {
    key: value
    // ...
};

2. 构造形式

// 用“构造形式”来创建对象非常少见
var myObj = new Object();
myObj.key = value;

(二)类型

1. JavaScript语言类型

JavaScript语言类型
console.log(typeof null); // "object"

// null 有时会被当作一种对象类型,但是这其实只是语言本身的一个bug。
// 实际上,null 本身是基本类型。

2. JavaScript内置对象

JavaScript内置对象

JavaScript 中,内置函数实际上只是一些内置函数。这些内置函数可以当作构造函数(由new 产生的函数调用)来使用,从而可以构造一个对应子类型的新对象。

// 字面量形式
var strPrimitive = "I am a string";
console.log(typeof strPrimitive); // "string"
console.log(strPrimitive instanceof String); // false

// 构造形式
var strObject = new String( "I am a string" );
console.log(typeof strObject); // "object"
console.log(strObject instanceof String); // true

// 字面量形式
var arrPrimitive = [];
console.log(typeof arrPrimitive); // "object"
console.log(arrPrimitive instanceof Array); // true

// 构造形式
var arrObject = new Array();
console.log(typeof arrObject); // "object"
console.log(arrObject instanceof Array); // true

有关对象更详细的介绍可查看《【JS基础】(十三)JavaScript三大对象两类属性》

(三)内容

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

存储在对象容器内部的是这些属性的名称,它们就像指针(从技术角度来说就是引用)一样,指向这些值真正的存储位置

1. 属性访问

  • . 操作符: “属性访问”
  • [] 操作符: “键访问”

这两种语法的主要区别在于:. 操作符要求属性名满足标识符的命名规范,而[] 语法可以接受任意UTF-8/Unicode 字符串作为属性名。当属性名为变量时(或者是表达式),只能使用[]操作符

var myObject = {
    a: 2
};
myObject.a; // 2
myObject["a"]; // 2

在对象中,属性名永远都是字符串。如果你使用string(字面量)以外的其他值作为属性名,那它首先会被转换为一个字符串。

var myObject = { };

myObject[true] = "foo";
myObject[3] = "bar";
myObject[myObject] = "baz";

console.log(myObject["true"]); // "foo"
console.log(myObject["3"]); // "bar"
console.log(myObject["[object Object]"]); // "baz"

2. 属性与方法

从技术角度来说,函数永远不会“属于”一个对象。

无论返回值是什么类型,每次访问对象的属性就是属性访问。如果属性访问返回的是一个函数,那它也并不是一个“方法”。属性访问返回的函数和其他函数没有任何区别(除了可能发生的隐式绑定this

function foo() {
    console.log( "foo" );
}

var someFoo = foo; // 对foo 的变量引用
var myObject = {
    someFoo: foo // 引用,不是复本!
};

console.log(foo);  // function foo(){..}
console.log(someFoo);  // function foo(){..}
console.log(myObject.someFoo);  // function foo(){..}

console.log(foo === someFoo ); // true
console.log(foo === myObject.someFoo ); // true
console.log(someFoo === myObject.someFoo ); // true

// someFoo 和myObject.someFoo只是对于同一个函数的不同引用,
// 并不能说明这个函数是特别的或者“属于”某个对象。
// 如果foo() 定义时在内部有一个this引用,
// 那这两个函数引用的唯一区别就是myObject.someFoo 中的this 会被隐式绑定到一个对象。
// 无论哪种引用形式都不能称之为“方法”。

即使你在对象的文字形式中声明一个函数表达式,这个函数也不会“属于”这个对象——
它们只是对于相同函数对象的多个引用

var myObject = {
    foo: function() { // 函数表达式
        console.log( "foo" );
    }
};

var someFoo = myObject.foo;

console.log(someFoo); // function foo(){..}
console.log(myObject.foo); // function foo(){..}

3. 复制对象

对对象使用=操作符进行的是浅复制

function anotherFunction() { /*..*/ }

var anotherObject = {
    c: true
};

var anotherArray = [];

var myObject = {
    a: 2,
    b: anotherObject, // 引用,不是复本!
    c: anotherArray, // 另一个引用!
    d: anotherFunction
};

(1) 对于JSON 安全(也就是说可以被序列化为一个JSON 字符串并且可以根据这个字符串解
析出一个结构和值完全一样的对象)的对象来说,有一种巧妙的复制方法(这种方法算是深复制):

var newObj = JSON.parse( JSON.stringify( someObj ) );

(2) ES6 定义了Object.assign()方法来实现浅复制。该方法的第一个参数是目标对象,之后还可以跟一个或多个源对象。它会遍历一个或多个源对象的所有可枚举的自有键并把它们复制(使用 = 操作符赋值)到目标对象,最后返回目标对象。

var newObj = Object.assign( {}, myObject );
newObj.a; // 2
newObj.b === anotherObject; // true
newObj.c === anotherArray; // true
newObj.d === anotherFunction; // true

4. 属性描述符

var myObject = {
    a:2
};

Object.getOwnPropertyDescriptor( myObject, "a" );

// {value: 2, writable: true, enumerable: true, configurable: true}

观察以上代码,对象属性对应的属性描述符(也被称为“数据描述符”,因为它只保存一个数据值)可不仅仅只是一个2。它还包含另外三个特性:

  • writable(可写)
  • enumerable(可枚举)
  • configurable(可配置)

也可以使用Object.defineProperty()来添加一个新属性或者修改一个已有属性(如果它是configurable)并对特性进行设置。

var myObject = {};

Object.defineProperty( myObject, "a", {
    value: 2,
    writable: true,
    configurable: true,
    enumerable: true
} );

myObject.a; // 2

(1) Writable

writable 决定是否可以修改属性的值。

var myObject = {};

Object.defineProperty( myObject, "a", {
    value: 2,
    writable: false, // 不可写!
    configurable: true,
    enumerable: true
} );

myObject.a = 3;
myObject.a; // 2

(2) Configurable

Configurable决定属性是否可配置。

  • 只要属性是可配置的,就可以使用defineProperty() 方法来修改属性描述符;
  • 若属性是不可配置的,那该属性也不被delete删除;
  • 要注意有一个小小的例外:即便属性是configurable:false,我们还是可以
    writable 的状态由true 改为false,但是无法由false 改为true
var myObject = {
    a:2
};
myObject.a = 3;
myObject.a; // 3

Object.defineProperty( myObject, "a", {
    value: 4,
    writable: true,
    configurable: false, // 不可配置!
    enumerable: true
} );

myObject.a; // 4
myObject.a = 5;
myObject.a; // 5,(表明writable: true的情况下,属性的值可以修改)

Object.defineProperty( myObject, "a", {
    value: 6,
    writable: true,
    configurable: true,
    enumerable: true
} ); // TypeError

(3) Enumerable

Enumerable控制属性是否会出现在对象的属性枚举中,比如说for..in循环。

如果把enumerable 设置成false,这个属性就不会出现在枚举中,虽然仍然可以正常访问它。

5. 不变性

多种方法实现属性或者对象不可改变:

(1) 对象常量

结合writable:falseconfigurable:false就可以创建一个真正的常量属性(不可修改、重定义或者删除)

var myObject = {};
Object.defineProperty( myObject, "FAVORITE_NUMBER", {
    value: 42,
    writable: false,
    configurable: false
} );

(2) 禁止扩展

var myObject = {
    a:2
};

Object.preventExtensions( myObject );

myObject.b = 3;
myObject.b; // undefined

(3) 密封

Object.seal() 会创建一个“密封”的对象,这个方法实际上会在一个现有对象上调用
Object.preventExtensions() 并把所有现有属性标记为configurable:false
所以,密封之后不仅不能添加新属性,也不能重新配置或者删除任何现有属性(但可以
修改属性的值)

(4) 冻结

Object.freeze() 会创建一个冻结对象,这个方法实际上会在一个现有对象上调用
Object.seal() 并把所有“数据访问”属性标记为writable:false,这样就无法修改它们的值。

6. GetterSetter

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

(1) [[Get]] : 在访问属性使用.[]操作符时,实际上是实现了[[Get]]操作。

对象默认的内置[[Get]]操作首先在对象中查找是否有名称相同的属性,如果找到就会返回这个属性的值。然而,如果没有找到名称相同的属性,则去其[[Prototype]]链上找。如果无论如何都没有找到名称相同的属性,那[[Get]] 操作会返回值undefined

(2)[[Put]]:如果已经存在这个属性,[[Put]] 算法大致会检查下面这些内容

  • 属性是否是访问描述符?如果是并且存在setter 就调用setter
  • 属性的数据描述符中writable 是否是false ?如果是,在非严格模式下静默失败,在严格模式下抛出TypeError 异常。
  • 如果都不是,将该值设置为属性的值。

访问描述符

当你给一个属性定义gettersetter或者两者都有时,这个属性会被定义为“访问描述符”(和“数据描述符”相对)。对于访问描述符来说,JavaScript会忽略它们的valuewritable特性,取而代之的是关心setget(还有configurableenumerable)特性。

var myObject = {
    // 给 a 定义一个getter
    get a() {
        return this._a_;
    },

    // 给 a 定义一个setter
    set a(val) {
        this._a_ = val * 2;
    }
};

myObject.a = 2;
myObject.a; // 4

7. 存在性

在不访问属性值的情况下判断对象中是否存在这个属性:

var myObject = {
    a:2
};
("a" in myObject); // true
("b" in myObject); // false
myObject.hasOwnProperty( "a" ); // true
myObject.hasOwnProperty( "b" ); // false
  • in 操作符会检查属性是否在对象及其[[Prototype]] 原型链中;
  • hasOwnProperty() 只会检查属性是否在myObject 对象中,不会检查[[Prototype]]链。
  • propertyIsEnumerable()会检查给定的属性名是否直接存在于对象中(而不是在原型链上)并且满足enumerable:true
  • Object.keys() 会返回一个数组,包含所有可枚举属性;
  • Object.getOwnPropertyNames()会返回一个数组,包含所有属性,无论它们是否可枚举。

(四)遍历

1. for...in

for...in 语句以任意顺序遍历一个对象的可枚举属性。对于每个不同的属性,语句都会被执行。每次迭代时,分配的是属性名。

let array2 = ['a','b','c']
let obj1 = {
  name : 'lei',
  age : '16'
}
 
for(variable  in array2){   //variable  为 index
  console.log(variable )   //0 1 2
}
 
for(variable  in obj1){   //variable 为属性名
  console.log(variable)   //name age
}

2. for...of

for...of会遍历具有iterator接口的数据结构,遍历(当前对象上的)每一个属性值。

Object.prototype.objCustom = function () {};
Array.prototype.arrCustom = function () {};
 
let iterable = [3, 5, 7];
iterable.foo = "hello";
 
for (let i in iterable) {
  console.log(i); // 0, 1, 2, "foo", "arrCustom", "objCustom"
}
 
for (let i of iterable) {
  console.log(i); // 3, 5, 7
}

有关数组对象的更多遍历的方法,可查看《【ES6】操作数组的常用方法有这些就够了》

相关文章

网友评论

    本文标题:【你不知道的JavaScript】(六)对象

    本文链接:https://www.haomeiwen.com/subject/fjhvrqtx.html