你不知道的JavaScript(上)this和对象原型(二)

第三章 对象

1、语法

两种形式定义:声明(文字)形式和构造形式

(1)文字语法大概是这样

1 var myObj = {
2     key: value
3     // ...
4 };

(2)构造形式大概是这样

1 var myObj = new Object();
2 myObj.key = value;

两者其实都是一样的,唯一区别就是,在文字声明中可以添加多个键值对,但是在构造形式中必须逐个添加

2、类型

javascript总共有六种主要类型(语言类型):string、 number、boolean、null、undefined、object 

(1)内置对象(javascript里面一些对象子类型)

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

实际上他们是内置函数,内置函数可以当作构造函数来使用,从而可以构造一个对应子类型的新对象

举个栗子,第一个的原始值"I am a string"并不是一个对象,只是一个字面量并且是一个不可变的值。如果要在这个字面量上执行一些操作,比如获取长度、访问其中某个字符窜等,都需要将其转换为String对象。

 

 

1 var strPrimitive = "I am a string";
2 typeof strPrimitive; // "string"
3 strPrimitive instanceof String; // false
4 var strObject = new String( "I am a string" ); 5 typeof strObject; // "object" 6 strObject instanceof String; // true 7 // 检查 sub-type 对象 8 Object.prototype.toString.call( strObject ); // [object String]

 

但是在必要时语言会自动把字符串字面量转换成一个 String 对象,也就是说你并不需要显式创建一个对象。引擎自动把字面量转换为String对象。

var strPrimitive = "I am a string";
console.log( strPrimitive.length ); // 13
console.log( strPrimitive.charAt( 3 ) ); // "m"

3、内容

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

属性和方法

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

1 var myObject = {
2 foo: function() {
3 console.log( "foo" );
4 }
5 };
6 var someFoo = myObject.foo;
7 someFoo; // function foo(){..}
8 myObject.foo; // function foo(){..}

4、数组

数组也是对象,所以虽然每个下表都是整数,但是仍然可以给数组添加属性(但是不推荐),

最好只用对象来存储键 / 值对,只用数组来存储数值下标 / 值对

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

5、复制对象(浅复制和深复制)

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

【由于 Object.assign(..) 就是使用 = 操作符来赋值,所以源对象属性的一些特性(比如 writable )不会被复制到目标对象】举个栗子:

 

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

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

 

6、属性描述

 

 

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

三个特性: writable (可写)、enumerable (可枚举)和 configurable (可配置)

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

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

7、Getter 和 Setter

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

 1 var myObject = {
 2 // 给 a 定义一个 getter
 3 get a() {
 4 return 2;
 5 }
 6 };
 7 Object.defineProperty(
 8 myObject, // 目标对象
 9 "b", // 属性名
10 { // 描述符
11 // 给 b 设置一个 getter
12 get: function(){ return this.a * 2 },
13 // 确保 b 会出现在对象的属性列表中
14 enumerable: true
15 }
16 );
17 myObject.a; // 2
18 myObject.b; // 4

通常来说 getter 和 setter 是成对出现的(只定义一个的话通常会产生意料之外的行为)

 1 var myObject = {
 2 // 给 a 定义一个 getter
 3 get a() {
 4   return this._a_;
 5 },
 6 // 给 a 定义一个 setter
 7 set a(val) {
 8   this._a_ = val * 2;
 9 }
10 };
11 myObject.a = 2;
12 myObject.a; // 4

8、枚举

举个栗子:

 1 var myObject = { };
 2 Object.defineProperty(
 3 myObject,
 4 "a",
 5 // 让 a 像普通属性一样可以枚举
 6 { enumerable: true, value: 2 }
 7 );
 8 Object.defineProperty(
 9 myObject,
10 "b",
11 // 让 b 不可枚举
12 { enumerable: false, value: 3 }
13 );
14 myObject.b; // 3
15 ("b" in myObject); // true
16 myObject.hasOwnProperty( "b" ); // true
17 // .......
18 for (var k in myObject) {
19 console.log( k, myObject[k] );
20 }
21 // "a" 2

myObject.b 确实存在并且有访问值,但是却不会出现在 for..in 循环中(尽管可以通过 in 操作符来判断是否存在)。原因是“可枚举”就相当于“可以出现在对象属性的遍历中”

还有另外一个方法可以区分属性是否可枚举

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" ); // false
Object.keys( myObject ); // ["a"]
Object.getOwnPropertyNames( myObject ); // ["a", "b"]
  • propertyIsEnumerable(..) 会检查给定的属性名是否直接存在于对象中(而不是在原型链上)并且满足 enumerable:true 。
  • Object.keys(..) 会返回一个数组,包含所有可枚举属性, Object.getOwnPropertyNames(..)会返回一个数组,包含所有属性,无论它们是否可枚举。
  • in 和 hasOwnProperty(..) 的区别在于是否查找 [[Prototype]] 链,然而, Object.keys(..)和Object.getOwnPropertyNames(..) 都只会查找对象直接包含的属性。

9、遍历

ES5 中增加了一些数组的辅助迭代器,包括 forEach(..) 、 every(..) 和 some(..) 。每种辅助迭代器都可以接受一个回调函数并把它应用到数组的每个元素上,唯一的区别就是它们对于回调函数返回值的处理方式不同。

1、forEach(..) 会遍历数组中的所有值并忽略回调函数的返回值。

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

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

但是上面的方法都只是遍历下标,怎样可以直接遍历值而不是数组的下标(或者对象属性)?噔噔噔噔...ES6增加了一种用来遍历数组的for..of循环语法【如果对此熬过本身定义了迭代器的话也可以遍历对象】:

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

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

1 ar myArray = [ 1, 2, 3 ];
2 var it = myArray[Symbol.iterator]();
3 it.next(); // { value:1, done:false }
4 it.next(); // { value:2, done:false }
5 it.next(); // { value:3, done:false }
6 it.next(); // { done:true }

但是,和数组不一样,普通的对象里面并没有内置对象@@iterator ,所以无法自动完成for..of遍历。但是可以给任何想遍历的对象定义 @@iterator

 1 var myObject = {
 2 a: 2,
 3 b: 3
 4 };
 5 Object.defineProperty( myObject, Symbol.iterator, {
 6 enumerable: false,
 7 writable: false,
 8 configurable: true,
 9 value: function() {
10 var o = this;
11 var idx = 0;
12 var ks = Object.keys( o );
13 return {
14 next: function() {
15 return {
16 value: o[ks[idx++]],
17 done: (idx > ks.length)
18 };
19 }
20 };
21 }
22 } );
23 // 手动遍历 myObject
24 var it = myObject[Symbol.iterator]();
25 it.next(); // { value:2, done:false }
26 it.next(); // { value:3, done:false }
27 it.next(); // { value:undefined, done:true }
28 // 用 for..of 遍历 myObject
29 for (var v of myObject) {
30 console.log( v );
31 }
32 // 2
33 // 3

 

posted @ 2018-12-13 11:31  KIU的博客  阅读(179)  评论(0编辑  收藏  举报