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. 内容
对象的内容是由一些存储在特定命名位置的任意类型的组成的,称之为属性
对象的属性名永远是字符串,
- 可计算属性名
可以在文字形式中使用[]包裹一个表达式当做属性名
var prefix = 'attributes'
var obj = {
[prefix + 'my']: 1,
[prefix + 'you']: 2
}
obj.attribitusmy // 1
obj['attributesyou'] // 2
复制代码
- 属性与方法
通常把属于某个对象(类)的函数叫做“方法”,实际上函数和对象之间只是一种引用的间接关系。
function foo () {
console.log('foo')
}
var bar = foo // 对foo变量的引用
var obj = {
bar: foo
}
obj.bar // function foo() {...}
bar // function foo() {...}
复制代码
obj.bar和bar都是对同一个函数的不同引用,不能说这个函数是特别地属于某一个对象。最保险的说法是“函数”和“方法”在js中是可以互换的。
- 数组
数组也是对象,虽然每个下标都是整数,但还是可以给数组增加属性
var arr = [1,2,3]
arr.attr = '新属性哦'
arr.length // 3
arr.attr // '新属性哦'
复制代码
虽然可以给数组增加属性,但这个属性的属性描述符enumerabe: false,是不可枚举的
- 复制对象
复制对象远比我们想象得复杂,因为我们无法选择一个默认的复制算法。 这也是面试中常问的如何浅拷贝和深拷贝一个对象。
对于基本类型的值,它的值就是存储在栈之中的,所以赋值操作就是直接把值复制过去,而对于引用类型的值来说,栈之中只是存储了一个指针,这个指针指向堆中真正存放的值。
对于引用类型的值,浅拷贝就是只需要拷贝值的指针,像赋值操作和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;
}
复制代码
- 属性描述符
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