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

(一)语法

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

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】操作数组的常用方法有这些就够了》

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容