最近在拜读《你不知道的js》,而此篇是对于《你不知道的js》中对象部分的笔记整理,希望能有效的梳理,并且深入理解对象

一、语法

对象两种定义形式:声明(文字)形式、构造形式

声明(文字)形式

var myObj = {key: value,...
}
复制代码

构造形式

var myObj = new Object();
myObj.key = value;
复制代码

构造形式与文字形式生成的对象一样

区别:文字声明中可以添加多个键/值对,构造形式中必须逐个添加属性

二、类型

在JavaScript中一共有6中主要类型:

  • string
  • number
  • boolean
  • null
  • undefined
  • object

注意:简单基本类型(string、boolean、number、null、undefined)本身不是对象。null有时会被当做一种对象类型,typeof null返回‘object’。实际上,null是基本类型

内置对象

内置对象:对象子类型

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

三、内容

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

var myObject = {a: 2
};
myObject.a;// 2
myObject['a']; // 2
复制代码

若要访问myObject中a位置上的值,需使用.操作符或[]操作符。.a语法称为属性访问(最常见的方式),['a']语法称为键访问

区别:.操作符要求属性名满足标识符的命名规范,而[".."]语法可以接受任意UTF-8/Unicode字符串作为属性名,如名称为“Super-Fun”的属性,就必须使用["Super-Fun"]语法访问

由于['..']语法使用字符串来访问属性,所以可以在程序中构造这个字符串,如:

var myObject = {a: 2
};
var idx;
if(wantA) {idx = "a";
}
console.log(myObject[idx]); // 2
复制代码

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

var myObject = {};
myObject[true] = 'foo';
myObject[3] = "bar";
myObject[myObject]="baz";
myObject["true"]; // 'foo'
myObject['3']; //"bar"
myObject["object object"]; // "baz"
复制代码

1、可计算属性名

ES6增加了可计算属性名:

var prefix = "foo";
var myObject = {[prefix + "bar"]: "hello",[prefix + "baz"]: "world"
};
myObject["foobar"];// hello
myObject["foobaz"];// world
复制代码

2、属性与方法

在其他语言中,属于对象(也称为“类”)的函数通常被称为“方法”,有时属性方位也称为“方法访问”。

无论返回值是什么类型,每次访问对象的属性就是属性访问。若属性访问返回一个函数,那它也并不是一个“方法”。属性访问返回的函数和其他函数没有任何区别

function foo() {console.log("foo");
}
var someFoo = foo; // 对foo变量的引用
var myObject = {someFoo: foo,
}
foo; // function foo() {}
someFoo; //  function foo() {}
myObject.someFoo; // function foo() {}
复制代码

someFoo与myObject.someFoo只是对于同一个函数的不同引用,并不能说明这个函数是特别的或“属于”某个对象

3、数组

数组也支持[]访问形式,通过数值下标,即存储位置(索引)访问,是非负整数,如:0,42:

var myArray = ["foo", 42, "bar"];
myArray.length; // 3
myArray[0]; // "foo"
myArray[2]; // "bar"
复制代码

数组也是对象,也可以给数组添加属性:

var myArray = ["foo", 42, "bar"];
myArray.baz = "baz";
myArray.length; // 3
myArray.baz; // "baz"
复制代码

虽然添加了命名属性,数组的length值并未发生变化。 若你试图向数组添加一个属性,但属性名“看起来”像数字,那它会变成一个数值下标:

var myArray = ["foo", 42, "bar"];
myArray["3"] = "baz";
myArray.length; // 4
myArray[3] = "baz";
复制代码

4、复制对象

function anotherFunction() {/*..*/}
var anotherObject = {c: true
}
var anotherArray = [];
var myObject = {a: 2,b: anotherObject, // 引用,不是复本c: anotherArray, // 另一个引用d: anotherFunction
}
anotherArray.push(anotherObject, myObject);
复制代码

如何准确地表示myObject的复制呢?

首先判断它是浅复制还是深复制。

1)浅复制 复制出的新对象中a的值会复制就对象中a的值,即2,但新对象中b、c、d三个属性其实只是三个引用,和就对象中b、c、d引用的对象一样 2)深复制 除了复制myObject以外还会复制anotherObject和anotherArray

问题:anotherArray引用了anotherObject和myObject,所以又需要复制,myObject,这样会由于循环引用导致死循环

如何解决?

1)对于json安全的对象来说:

var newObj = JSON.parse(JSON.stringify(someObj));
复制代码

这种方法需要保证对象是json安全的,所以只适用于部分情况。

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

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

注:由于Object.assign(...)使用 = 操作符来赋值,所以源对象属性的一些特性不会被复制到目标对象。

5、属性描述符

在ES5之前,JavaScript语言本身并没有提供可直接检测属性特性的方法,如判断属性是否是只读。从ES5开始,所有的属性都具备了属性描述符。

var myObject = {a: 2
}
Object.getOwnPropertyDescriptor(myObject, "a");
// {
//   value: 2,
//   writable: true, // 可写
//   enumerable: true, // 可枚举
//   configurable: true // 可配置
// }
复制代码

在创建普通属性时属性描述符会使用默认值,可使用Object.defineProperty(...)来添加一个新属性或修改一个已有属性,并对特性进行设置。

var myObject = {};
Object.defineProperty(myObject, "a", {value: 2,writable: true,configurable: true,enumerable: true
});
myObject.a; // 2
复制代码

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

var myObject = {};
Object.defineProperty(myObject, "a", {value: 2,writable: false, // 不可写configurable: true,enumerable: true
});
myObject.a = 3;
myObject.a; // 2
复制代码

在严格模式下会报错

"use strict"
var myObject = {};
Object.defineProperty(myObject, "a", {value: 2,writable: false, // 不可写configurable: true,enumerable: true
});
myObject.a = 3; // TypeError
复制代码

2)Configurable 只要属性可配置,就可以用defineProperty(...)方法修改属性描述符:

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
Object.defineProperty(myObject, "a", {value: 6,writable: true,configurable: true,enumerable: true
}); // TypeError
复制代码

无论是否处于严格模式,尝试修改一个不可配置的属性描述符都会出错。

注:把Configurable 修改成false是单向操作,无法撤销; 即便属性是configurable:false,我们还是可以把writable的状态由true改为false,但无法由false改为true。

除了无法修改,configurable: false还会禁止删除这个属性:

var myObject = {a: 2
};
myObject.a = 2;
delete myObject.a;
myObject.a; // undefined
Object.defineProperty(myObject, "a", {value: 2,writable: true,configurable: false, // 不可配置enumerable: true
});
myObject.a; // 2
delete myObject.a;
myObject.a; // 2
复制代码

在本例中,delete只用来直接删除对象的(可删除)属性。若对象的某个属性是某个对象/函数的最后一个引用者,对这个属性执行delete操作后,这个对象/函数就可以被垃圾回收

3)enumerable 控制属性是否出现在对象的属性枚举类中,如for...in循环,若把enumerable设置为false,属性就不会出现在枚举中,虽然仍可以正常访问它

6、不变性

ES5中所有方法创建的都是浅不变性,即它们只会影响目标对象和它的直接属性。如果目标对象引用了其他对象(数组、对象、函数等),其他对象的内容不受影响,仍是可变的:

myImmutableObject.foo; // [1,2,3]
myImmutableObject.foo.push(4);
myImmutableObject.foo; // [1,2,3,4]
复制代码

1)对象常量

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

var myObject = {};
Object.defineProperty(myObject, "FAVORITE_NUMBER", {value: 42,writable: false,configurable: false, // 不可配置
});
复制代码

2)禁止扩展

Object.preventExtensions(...):禁止一个对象添加新属性并且保留已有属性

var myObject = {a: 2
};
Object.preventExtensions(myObject);
myObject.b = 3;
myObject.b; // undefined
复制代码

非严格模式下,创建属性b会静默失败,在严格模式下,将抛出TypeError

3)密封

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

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

这个方法可应用在对象上的级别最高的不可变性,它会禁止对象本身及其任意直接属性的修改,这个对象的引用的其他对象是不受影响的

深度冻结方法:首先在这个对象上调用Object.freeze(...),然后遍历它引用的所有对象并在这些对象上调用Object.freeze(...),但可能会在无意中冻结其他(共享)对象

7、[[Get]]

var myObject = {a: 2
}
myObject.a; // 2
复制代码

myObject.a在myObject上实际是实现了[[Get]]操作。对象默认的内置[[Get]]操作首先在对象中查找是否有名称相同的属性,如果找到就会返回这个属性的值,若没找到,按照[[Get]]实验法的定义会执行另外一种非常重要的行为,即遍历可能存在的[[Prototype]]链,也就是原型链。

如果无论如何都没有找到名称相同的属性,那[[Get]]操作会返回undefined

var myObject = {a: 2
}
myObject.b; // undefined
复制代码

注:这种方法和访问变量时是不一样的,若你引用了一个当前词法作用域中不存在的变量,并不会像对象属性一样返回undefined,而是会抛出ReferenceError异常:

var myObject = {a: undefined
}
myObject.a; // undefined
myObject.b; // undefined 由于根据返回值无法判断出到底变量的值为undefined还是变量不存在,所以[[Get]]操作返回了undefined
复制代码

8、[[Put]]

[[Put]]被触发时,实际行为取决于许多因素,包括对象中是否已经存在这个属性(最重要的因素)

如果已经存在这个属性,[[Put]]算法大致会检查下面这些内容: 1)属性是否是访问描述符?如果是并且存在setter就调用setter 2)属性的数据描述符中writable是否是false?如果是,在非严格模式下静默注册失败,在严格模式下抛出TypeError异常

9、Getter和Setter

在ES5中可使用getter和setter部分改写默认操作,但只能应用在单个属性上,无法应用在整个对象上。getter是一个隐藏函数,会在获取属性值时调用。setter也是一个隐藏函数,会在设置属性时调用。

当你给一个属性定义getter和setter或者两者都有时,这个属性会被定义为“访问描述符”,对于访问描述符来说,js会忽略它们的value和writable特性,取而代之的关心set和get(还有configurable和enumerable)

var myObject = {get a() {return 2;}
}
Object.defineProperty(myObject, // 目标对象"b", // 属性名{get: function() {return this.a * 2},enumerable: true}
);
myOject.a; // 2
myObject.b; // 4
复制代码

两种方式都会在对象中创建一个不包含值的属性,对于这个属性的访问会自动调用一个隐藏函数,它的返回值会被当做属性访问的返回值:

var myObject = {get a() {return 2;}
}
myOject.a = 3;
myObject.a; // 2
复制代码

由于我们只定义了a的getter,所以对a的值进行设置时set操作会忽略赋值操作,不会抛出错误。

通常来说getter和setter是成对出现的

var myObject = {get a() {return this._a_;}set a(val) {this._a_ = val * 2;}
}
myOject.a = 2;
myObject.a; // 4
复制代码

10、存在性

属性访问返回值可能是undefined,如何区分这是属性中存储的undefined,还是属性不存在而返回的undefined ?

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]]链

注:in是检查某个属性名是否存在,如:4 in [2,4,6] = false, 因为这个数组中包含的属性名为0 ,1, 2

所有的普通对象都可以通过对于 Object.prototype 的委托来访问hasOwnProperty(..),但有的对象可能没有连接到 Object.prototype (通过Object.create(null)创建),则myObject.hasOwnProperty就会失败

此时可采用 Object.prototype.hasOwnProperty.call(myObject, "a") 进行判断,它借用基础的hasOwnProperty(..)方法并把它显示绑定在myObject上

1)枚举

“可枚举”相当于“可以出现在对象属性的遍历中”

var myObject = {};
Object.defineProperty(myObject, "a",// 让a像普通对象一样可枚举{ enumerable: true, value: 2 }
);
Object.defineProperty(myObject, "B",// 让b不可枚举{ enumerable: false, value: 3 }
);
myObject.b; // 3
("b" in myObject); // true
myObject.hasOwnProperty("b"); // truefor (var k in myObject) {console.log(k, myObject[k]);
}
// "a" 2
复制代码

for...in枚举不仅会包含所有索引,还会包含所有可枚举属性,所以最好只在对象上引用for ... in 循环,若遍历数组就使用for循环。但它无法直接获取属性值,需手动获取

也可用propertyIsEnumerable(..)来区分属性是否可枚举

var myObject = {};
Object.defineProperty(myObject, "a",// 让a像普通对象一样可枚举{ enumerable: true, value: 2 }
);
Object.defineProperty(myObject, "B",// 让b不可枚举{ enumerable: false, value: 3 }
);
myObject.propertyIsEnumerable("a"); // true
myObject.propertyIsEnumerable("b"); // falseObject.keys(myObject); // ["a"]
Object.getOwnPropertyNames(myObject); // ["a", "b"]
复制代码

propertyIsEnumerable(..)会检查给定的属性名是否直接存在于对象中(而非原型链上),并且满足enumerable: true

Object.keys(..)会返回数组,包含所有可枚举属性。Object.getOwnPropertyNames(..)只会查找对象直接包含的属性

四、遍历

for...in用来遍历对象的可枚举属性列表,如何遍历属性值呢?

对于数值索引的数组来说,可用for循环。另外ES5也增加了一些数组辅助迭代器:forEach(..)、every(..)、some(..),他们都可以接受一个回调函数并把它应用在数组的每个元素上,区别就是它们对于回调函数返回值的处理方式不同

forEach(..):遍历数组所有值并忽略回调函数的返回值

every(..):会一直运行到回调函数返回false(或“假”值)

some(..):会一直运行直到回调函数返回true(或“真”值)

遍历数组下标时采用的数字顺序,但遍历对象属性时顺序不确定,在不同的js引擎中可能不一样

如何直接遍历值而不是数组下标?

使用for..of (ES6增加的语法)

var myArray = [1, 2, 3];
for(var v of myArray) {console.log(v);
}
// 1
// 2
// 3
复制代码

for..of首先会向被访问对象请求一个迭代器对象,然后通过调用迭代器对象的next()方法来遍历所有返回值

数组有内置的@@iterator,也可直接应用在数组上。

var myArray = [1, 2, 3];
var it = myArray[Symbol.iterator]();
it.next(); // {value: 1, done: false}
it.next(); // {value: 2, done: false}
it.next(); // {value: 3, done: false}
it.next(); // {done: true}
复制代码

value是遍历值,done是布尔值,表示是否还有可遍历的值

普通对象中没有内置的@@iterator,所以无法自动完成for..of,但可以结合for..of循环与自定义迭代器来操作对象

var myObject = {a: 2,b: 3
}
Object.defineProperty(myObject, Symbol.iterator, {enumerable: false,writable: false,configurable: true,value: function() {var o = this;var idx = 0;var ks = Object.keys(o);return {next:function() {return {value: o[ks[idx++]],done: (idx > ks.length)}}}}
});
// 手动遍历
var it = myObject[Symbol.iterator]();
it.next(); // {value: 2, done: false}
it.next(); // {value: 3, done: false}
it.next(); // {value: undefined, done: true}// for .. of
for (var v of myObject) {conosle.log(v);
}
// 2
// 3
复制代码

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

对象----《你不知道的JS》相关推荐

  1. 翻译连载 | JavaScript轻量级函数式编程-第7章: 闭包vs对象 |《你不知道的JS》姊妹篇...

    原文地址:Functional-Light-JS 原文作者:Kyle Simpson-<You-Dont-Know-JS>作者 关于译者:这是一个流淌着沪江血液的纯粹工程:认真,是 HTM ...

  2. 翻译连载 | JavaScript轻量级函数式编程-第4章:组合函数 |《你不知道的JS》姊妹篇...

    原文地址:Functional-Light-JS 原文作者:Kyle Simpson-<You-Dont-Know-JS>作者 关于译者:这是一个流淌着沪江血液的纯粹工程:认真,是 HTM ...

  3. 翻译连载 | 附录 A:Transducing(下)-《JavaScript轻量级函数式编程》 |《你不知道的JS》姊妹篇...

    原文地址:Functional-Light-JS 原文作者:Kyle Simpson-<You-Dont-Know-JS>作者 关于译者:这是一个流淌着沪江血液的纯粹工程:认真,是 HTM ...

  4. 你不知道的JS之作用域和闭包(二)词法作用域

    原文:你不知道的js系列 词法作用域(Lexical Scope) Lex time 一个标准的编译器的第一个阶段就是分词(token化) 词法作用域就是在词法分析时定义的作用域.换句话说,词法作用域 ...

  5. 翻译连载 | JavaScript轻量级函数式编程-第5章:减少副作用 |《你不知道的JS》姊妹篇...

    原文地址:Functional-Light-JS 原文作者:Kyle Simpson-<You-Dont-Know-JS>作者 关于译者:这是一个流淌着沪江血液的纯粹工程:认真,是 HTM ...

  6. 翻译连载 | 附录 B: 谦虚的 Monad-《JavaScript轻量级函数式编程》 |《你不知道的JS》姊妹篇...

    为什么80%的码农都做不了架构师?>>>    原文地址:Functional-Light-JS 原文作者:Kyle Simpson-<You-Dont-Know-JS> ...

  7. 翻译连载 | 第 11 章:融会贯通 -《JavaScript轻量级函数式编程》 |《你不知道的JS》姊妹篇...

    原文地址:Functional-Light-JS 原文作者:Kyle Simpson-<You-Dont-Know-JS>作者 关于译者:这是一个流淌着沪江血液的纯粹工程:认真,是 HTM ...

  8. 翻译连载 | JavaScript轻量级函数式编程-第 8 章:列表操作 |《你不知道的JS》姊妹篇

    原文地址:Functional-Light-JS 原文作者:Kyle Simpson-<You-Dont-Know-JS>作者 关于译者:这是一个流淌着沪江血液的纯粹工程:认真,是 HTM ...

  9. 翻译连载 | JavaScript轻量级函数式编程-第 8 章:列表操作 |《你不知道的JS》姊妹篇...

    原文地址:Functional-Light-JS 原文作者:Kyle Simpson-<You-Dont-Know-JS>作者 关于译者:这是一个流淌着沪江血液的纯粹工程:认真,是 HTM ...

最新文章

  1. #161: 给定n*n由0和1组成的矩阵,如果矩阵的每一行和每一列的1的数量都是偶数,则认为符合条件。 你的任务就是检测矩阵是否符合条件...
  2. 消息中间体activeMQ
  3. 客户端相关知识学习(三)之Android原生与H5交互的实现
  4. Qt 常用类——QStandardItemModel
  5. html 圆圈项目符号,html – 列表项下的项目符号
  6. wechat.php+获取昵称,微信后台代码,获取用户昵称
  7. 【干货分享】自己总结录制的web前端精讲视频,零基础入门学习资料,开发工具
  8. PHPCMS整合UCENTER后登陆问题
  9. Objective-C对象模型及应用
  10. 电脑常用音频剪辑软件_常用手机视频剪辑软件
  11. 人事管理系统(毕业设计)
  12. python调用canape_基于CCP协议利用CANape进行电控单元标定
  13. 计算机单词 硬件类、软件类、网络类、其他
  14. 贝叶斯网络分析kaggle泰坦尼克号数据
  15. c语言编程实现电脑关机,C语言让电脑关机?system函数功能够大够硬
  16. Java 文件上传同时携带参数
  17. 鸿蒙时代实力排名,混沌氏(浑沌)、鸿蒙氏,盘古开天辟地时两个最强大的部落首领?...
  18. 计算机识别不到硬盘,解决BIOS检测不到硬盘的问题
  19. poi java 导入excel_Java的poi技术读取和导入Excel
  20. android wifi热点默认名称,Android 修改WiFi热点的默认SSID和密码

热门文章

  1. 使用APIHOOK实现进程隐藏
  2. json vue 对象转数组_vue 基础入门(一)修改
  3. Java项目:学生管理系统(无库版)(java+打印控制台)
  4. c语言自定义char*函数返回值是乱码_[每日C语言」printf()函数的修饰符和返回值...
  5. 【js】将json类型的数组或对象转为字符串
  6. XML 解析XML文档 XML约束
  7. 振动力学基础与matlab应用_【日文好书推荐】振动与噪声控制技术for机械设计者...
  8. 微信小程序 scroll-view 根据内容的高度自适应
  9. Promise的实例用法
  10. 一个6年iOS程序员的工作感悟,送给还在迷茫的你