js中对象

111 阅读7分钟

一、内置对象

• String • Number • Boolean • Object • Function • Array • Date • RegExp • Error
这些内置函数可以当作构造函数(由new产生的函数调用)来使用,从而可以构造一个对应子类型的新对象。

var strPrimitive = "I am a string"; //只是一个字面量且不可变,要操作要换成string对象
console.log(typeof strPrimitive);             //String
console.log(strPrimitive instanceof String);  //false

var str =new String("I am a string");
console.log(typeof str);                      //object
console.log(str instanceof String);           //true

1.1 typeof和instanceof的区别?

1、typeof判断所有变量的类型,返回值有number、string、boolean、function、object、undefined。
2、typeof对于丰富的对象实例,只能返回object,导致有时候得不到真实的数据类型。
3、instanceof用来判断对象,代码形式(obj1 instanceof obj2)(判断obj1是否为obj2的实例),obj2必须为对象,否则会报错。返回的是布尔值。
4、instanceof可以对不同的实例对象进行判断,判断方法是根据对象的原型链依次向下查询,如果obj2的原型属性存在于obj1的原型链上,(obj1 instanceof obj2)值为true。

1.2 typeof null时会返回字符串 "object"?

不同的对象在底层都表示为二进制,在JavaScript中二进制前三位都为 0 的话会被判断为 object 类型,null 的二进制表示是全 0,自然前三位也是 0,所以执行 typeof 时会返回“object”。

二、对象属性

存储在对象容器内部的是这些属性的名称,它们就像指针(引用)一样,指向这些值真正的存储位置。
在对象中属性名永远都是字符串。如果使用 string以外的其他值作为属性名,首先会被转换为一个字符串。

2.1 可计算属性

ES6 增加了可计算属性名,可以在文字形式中使用[]包裹一个表达式来当作属性名,最常用的场景是 ES6 的符号(Symbol)

var str = "foo";
var myObject = {
        [str + "foo"] : "hi",
}
myObject["foofoo"]    //'hi'

2.2 属性与方法

无论返回值是什么类型,每次访问对象的属性就是属性访问。如果属性访问返回的是一个函数,那它也并不是一个“方法”。

2.3 复制对象

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

对于深复制来说,除了复制 myObject 以外还会复制old和newarray。这时问题就来了,newArray 引用了old和myObject,所以又需要复制 myObject,这样就会由于循环引用导致死循环。 被序列化为一个 JSON 字符串并且可以根据这个字符串解析出一个结构和值完全一样的对象)的对象来说,有一种巧妙的复制方法:var newObj = JSON.parse( JSON.stringify( someObj ) );
对于浅拷贝来说,a=2会被复制到新数组中,但是b、c、d 三个属性其实只是三个引用,它们和旧对象中 b、c、d 引用的对象是一样的。
ES6 定义了 Object.assign(..) 方法来实现浅复制

var newObj = Object.assign( {}, myObject );
console.log(newObj.a);//2
console.log(newObj.b === old);   //true
console.log(newObj.c === newArray);   //true
console.log(newObj.d === anotherFunction);   //true

2.4 属性描述符

从 ES5 开始,所有的属性都具备了属性描述符

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

Object.defineProperty(..)来添加一个新属性或者修改一个已有属性

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

2.5 不变性

希望属性或者对象是不可改变,所有的方法创建的都是只会影响目标对象和它的直接属性。如果目标对象引用了其他对象(数组、对象、函数,等),其他对象的内容不受影响,仍然是可变的

对象常量

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

禁止扩展

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

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

密封

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

var myObject = {
	a:2,
}
Object.seal(myObject)
Object.getOwnPropertyDescriptor(myObject,"a")
//{value: 2, writable: true, enumerable: true, configurable: false}

冻结

Object.freeze()会创建一个冻结对象,这个方法实际上会在一个现有对象上调用Object.seal()并把所有“数据访问”属性标记为 writable:false,这样就无法修改它们的值。会禁止对于对象本身及其任意直接属性的修改(这个对象引用的其他对象是不受影响的)

var myObject = {
	a:2,
}
Object.freeze(myObject)
Object.getOwnPropertyDescriptor(myObject,"a")
//{value: 2, writable: false, enumerable: true, configurable: false}

2.6 [[Get]]

对象默认的内置 [[Get]] 操作首先在对象中查找是否有名称相同的属性,如果找到就会返回这个属性的值。如果没有找到名称相同的属性,遍历可能存在的 [[Prototype]] 链,也就是原型链。

var myObject = {
    a: 2
};
myObject.a; // 2

2.7 [[Put]]

如果已经存在这个属性,[[Put]]会检查下面这些内容。

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

2.8 Getter和Setter

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

var myObject = {       // 给 a 定义一个 getter 
get a() { 
	return 2;      //  ==myObject.a = 2;
	}
 };
 { // 描述符  
 get: function(){       // 给 b 设置一个 getter
	 return this.a * 2 
 },                    // 确保 b 会出现在对象的属性列表中 
 enumerable: true } );
 myObject.a; // 2 
 myObject.b; // 4

设置setter

var myObject = {
    get a() {              // 给 a 定义一个 getter
        return this._a_;
    },
    set a(val) {          // 给 a 定义一个 setter
        this._a_ = val * 2;
    }
};
myObject.a = 2;
myObject.a; // 4

2.9 存在性

myObject.a的属性访问返回值可能是 undefined,但是这个值有可能是属性中存储的 undefined,也可能是因为属性不存在所以返回 undefined。那么如何区分这两种情况呢?

var myObject = {
    a:2
};
("a" in myObject); // true     in操作符会检查属性是否在对象及其[[Prototype]]原型链中
("b" in myObject); // false
myObject.hasOwnProperty( "a" ); // true     hasOwnProperty()只会检查属性是否在myObject对象中,不会检查[[Prototype]]链。
myObject.hasOwnProperty( "b" ); // false
Object.keys(myObject)  //['a']
Object.getOwnPropertyNames(myObject) //['a']  都只会查找对象直接包含的属性。

三、遍历

for..in 循环可以用来遍历对象的可枚举属性列表(包括 [[Prototype]] 链)。遍历属性值?

遍历数组下标

采用的是数字顺序(for 循环或者其他迭代器),但是遍历对象属性时的顺序是不确定
ES5 中增加了一些数组的辅助迭代器,包括 forEach(..)、every(..) 和 some(..)区别就是它们对于回调函数返回值的处理方式不同。

forEach(..) 会遍历数组中的所有值并忽略回调函数的返回值
every(..) 会一直运行直到回调函数返回 false(或者“假”值)
some(..) 会一直运行直到回调函数返回 true(或者“真”值)。
every(..) 和 some(..) 中特殊的返回值和普通 for 循环中的 break 语句类似,它们会提前终止遍历。
使用 for..in 遍历对象是无法直接获取属性值的,因为它实际上遍历的是对象中的所有可枚举属性,你需要手动获取属性值。

直接遍历值

ES6 增加了一种用来遍历数组的for..of循环语法(若对象本身定义了迭代器的话也可以遍历对象):

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

for..of 循环首先会向被访问对象请求一个迭代器对象,然后通过调用迭代器对象的next() 方法来遍历所有返回值。
数组有内置的 @@iterator,因此 for..of 可以直接应用在数组上。Symbol.iterator 来获取对象的 @@iterator 内部属性。

image.png