我们在前面几章中介绍了this的绑定,说明了this最常被搞混的观点也介绍了如何透过call-site与绑定的4个规则确定this所指向的物件是那一个,介绍了这麽多this所指向的物件位置,那麽物件到底是什麽?而为什麽我们的this会需要指向它呢?我们会在本篇章中进行讲解。
物件有两种形式:declaration与constructed。
var myObj = {
key: value
// ...
};
var myObj = new Object();
myObj.key = value;
使用两种方法产生的物件完全相同,他们唯一的区别在於如果你使用declaration可以在物件中添加一个或多个key/value
,但是使用constructed只能将属性一个一个加入
。
物件是构建大部分JS的通用模块,他是JS中6种主要类型之一。
null有时候会被当成一个物件类型,这种误解来自於JS中的一个bug使得typeof null
会回传object,但这是不对的,因为null有属於自己的类型,还有一个常见的错误判断JavaScript中的一切都是物件
,这是不对的。
除了上面的六种着要的型态之外,还有一些特殊的存在称为物件子类别(复杂基本类型)
,function是物件的一种子类型,function在JS中被称为first class
类型,因为他们基本上就是普通的物件而且可以被当作其他物件一样处理,而阵列也是一种形式的物件,他在内容的组织的结构化上会比一般物件好。
还有其他物件子类别通常称为内置物件
,它们的名称看起来和它们对应的基本类型有联系,但事实上它们的关系更复杂。
var strPrimitive = "I am a string";
typeof strPrimitive; // "string"
strPrimitive instanceof String; // false
var strObject = new String( "I am a string" );
typeof strObject; // "object"
strObject instanceof String; // true
基本类型的"I am a string"他是一个不可变的字串而不是物件,为了对这个字串进行操作(检查长度...)就会需要将这个字串变为物件形式,但是幸运的事JS对於这种情况,他会在需要的时候自动的
将"string"
强制转换为String
类型(auto-boxing),这意味着你不需要明确的创建这个字串的物件就可以对他进行操作。
var strPrimitive = "I am a string"; // type -> "string"
// auto transform to String(object)
console.log( strPrimitive.length ); // 13
console.log( strPrimitive.charAt( 3 ) ); // "m"
对於这种自动转换型别的也发生在number与boolean,但是null与undefined没有物件形式
,他们只有自己的基本类型,Date只能透过new建立所以他没有基本型态。
无论使用declaration还是constructed建立Objects
、Array
、Function
、RegExps
他们都是物件。
Error
很少明确且直接的被创建出来,通常在有异常的时候自动被创建并且掷出,可以由new Error(...)
建立出来不过很少见。
物件的内容会储存在物件中某些特定命名的位置上,我们称这些储存在物件中的值为properties
。
虽然我们说内容是存在於物件之中,但其实这只是一种看起来
而已,对JS来说他是以依赖(implementation-dependent)
的方式储存并且很有可能不将内容储存在物件容器中
,只有这些properties的名称储存在容器
,而这些properites名称会当作指向储存内容的位置的指针,换句话说储存在物件容器内的properties名称是物件内容的Reference
。
var myObject = {
a: 2
};
myObject.a; // 2
myObject["a"]; // 2
若要放问到myObject中的a需要使用.
或[]
运算符,.a
通常用於取得物件的property,而["a"]
用於键(key)的访问,实际上这两个用法访问到的位置是相同的所以都可以使用。
两种访问最主要的区别在於,如果使用.
则後面需要一个兼容标识符(Identifier)
的属性名称,而[".."]
中则可以接收任何兼容UTF-8/unicode的字串作为属性名,举个例子若你的物件中有个Super-Fun!
属性,就只能使用["Super-Fun!"]来访问这个属性,因为他不是一个合法的标识符(Identifier)。
由於["..."]
是使用字串
所以可以在程序中动态的变更我们需要访问的位置
var wantA = true;
var myObject = {
a: 2,
b: 3
};
var idx;
if (wantA) {
idx = "a";
}
console.log( myObject[idx] ); // 2
由於物件的属性必须
得是字串,所以当你填入非字串的属性名则会优先将它转变为字串
,转换的范围甚是包夸number。
var myObject = { };
myObject[true] = "foo";
myObject[3] = "bar";
myObject[myObject] = "baz";
myObject["true"]; // "foo"
myObject["3"]; // "bar"
myObject["[object Object]"]; // "baz"
使用["..."]
还可以对键(key)进行操作,比如说Object[prefix + name]。
var prefix = "foo";
var myObject = {
[prefix + "bar"]: "hello",
[prefix + "baz"]: "world"
};
myObject["foobar"]; // hello
myObject["foobaz"]; // world
对於在物件中的function来说,许多开发者将他与property区分开来,我们称它为method,因为以技术上来说function他其实是不属於物件
,他在物件中只是以一个Reference的形式储存,所以当物件访问一个function的时候就很像是一个方法(method)
,虽然这是一个满牵强的理由XD。
阵列也使用[]
来访问其中的元素,但是阵列在储存值以及储存位置的结构上更具有组织性,阵列采用数字索引
这意味着这个元素被储存的位置,必须是一个非负整数
。
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"
虽然阵列可以达到与物件一样的效果(增加键/值)但是不推荐做这种操作,因为阵列本身有他的用途与使用方法,所以建议用物件来储存键/值而不适用阵列。
还有一个直得注意的地方,虽然我们对myArray
添加了属性,但是可以发现myArray.length
的长度并没有被改变,但是如果在阵列的属性中添加的值看起来像个数字,则他最终会变成阵列的索引
。
var myArray = [ "foo", 42, "bar" ];
myArray["3"] = "baz";
myArray.length; // 4
myArray[3]; // "baz"
除了会更改阵列长度之外,如果添加的数字属性名是已经存在於阵列中的index,则会改变其阵列的内容
var myArray = [ "foo", 42, "bar" ];
myArray["1"] = "baz";
myArray.length; // 3
myArray[1]; // "baz"
在我们创建了一个物件时,可能会面临到需要复制物件的情况,一开始可能会觉得就单纯将这个物件复制过去(就跟一般的value一样),但是其实JS的物件复制比这个来的复杂多了,要介绍物件的复制首先要先区分浅拷贝
与深拷贝
的区别。
对於复制物件来说,obj1 = obj2
他所传递的不是obj2的值而是obj2的Reference
,这意味着他们是共用同一个记忆体空间,所以当一个更改了另一个也会被影响而一同变更
。
var obj1 = { a: 10, b: 20, c: 30 };
var obj2 = obj1;
obj2.b = 100;
console.log(obj1); // { a: 10, b: 100, c: 30 } <-- b 被改到了
console.log(obj2); // { a: 10, b: 100, c: 30 }
(图片来源 : [Javascript] 关於 JS 中的浅拷贝和深拷贝)
深拷贝与浅拷贝的只复制Reference不同,他会创造
一个新的物件,新物件与旧物件不会共用一个记忆体空间,所以修改新物件不会同步影响到旧物件。
var obj1 = { a: 10, b: 20, c: 30 };
var obj2 = { a: obj1.a, b: obj1.b, c: obj1.c };
obj2.b = 100;
console.log(obj1); // { a: 10, b: 20, c: 30 } <-- b 没被改到
console.log(obj2); // { a: 10, b: 100, c: 30 }
(图片来源 : [Javascript] 关於 JS 中的浅拷贝和深拷贝)
介绍完什麽是浅拷贝与深拷贝後,我们回到本书
function anotherFunction() { /*..*/ }
var anotherObject = {
c: true
};
var anotherArray = [];
var myObject = {
a: 2,
b: anotherObject, // reference, not a copy!
c: anotherArray, // another reference!
d: anotherFunction
};
anotherArray.push( anotherObject, myObject );
将过介绍什麽是深浅拷贝後,可以发现myObject
中的b、c、d都不是物件的复制而只是共用了相同的Reference(浅拷贝),如果要将b、c、d深拷贝给myObject,有一个解决方法就是使用JSON,将物件使用JSON.stringify
转变为字串後再用JSON.parse
转回物件,这样就可以得到一个深拷贝。
var obj1 = { body: { a: 10 } };
var obj2 = JSON.parse(JSON.stringify(obj1));
obj2.body.a = 20;
console.log(obj1); // { body: { a: 10 } } <-- 没被改到
console.log(obj2); // { body: { a: 20 } }
console.log(obj1 === obj2); // false
console.log(obj1.body === obj2.body); // false
还有另一种深拷贝的方法,ES6提供了一个新函数Object.assign
。
var obj1 = { a: 10, b: 20, c: 30 };
var obj2 = Object.assign({}, obj1);
obj2.b = 100;
console.log(obj1); // { a: 10, b: 20, c: 30 } <-- 没被改到
console.log(obj2); // { a: 10, b: 100, c: 30 }
Object.assign({},obj1)
的第一个参数{}代表他会建立一个空的物件,接着再把obj1中的properties复制过去,所以obj2会长得跟obj1一样但是却不是共用同一个记忆体位置,不过要注意的是Object.assign只能复制一层
的物件。
除了使用ES6提供的Object.assign
之外,也可以使用ES6提供的...(展开运算子spread operator)
将obj1的物件复制到空物件中
var obj1 = { a: 10, b: 20, c: 30 };
var obj2 = {
...obj1, // 展开obj1并复制
b: 100, // 更改obj2中的值
}
console.log(obj1); // { a: 10, b: 20, c: 30 } <-- 没被改到
console.log(obj2); // { a: 10, b: 100, c: 30 }
在本章节中我们介绍了物件是什麽、型态与一些特性,让我们来整理一下
declaration建立的物件可以一次性的加入一个或多个数性
,而constructed只能一个一个加入
。.
与["..."]
访问到物件的properties,他们的区别在於.
需要符合Identifier而["..."]
只要是UTF-8/unicode的字串都可以。property
,函数称为method
。如果属性名称是数字则会改变阵列index的情况
。深拷贝
与浅拷贝
,浅拷贝只是复制物件的Reference所以是共用同一个记忆体位置; 深拷贝是创造一个新的物件。参考文献:
You Don't Know JavaScript
[Javascript] 关於 JS 中的浅拷贝和深拷贝
<<: 全方位对比:SmartQuery VS FineReport来自报表工程师的经验
>>: 【影片】Windows 搜寻框输入命令快速开启 VSCode (code.cmd.)
表格标签主要用来显示以及展示数据,可用表格标签排版後让数据更容易阅读 1. 表格基础标签简易介绍 (...
在上一篇文章介绍了ManoMotion的安装与介绍,今天我们要使用ManoMotion来制作打地鼠游...
什麽是live server? 如果有看我之前的文章就会知道它是一个vscode的插件,可以即时预览...
我相今天的篇章是大家期待已久的,因为经过前面十天的努力,今天终於要将我们的部落格公开在世人面前啦!不...
前言 阵列是一种资料结构,里面装载的资料必须是同性质的,不能同时装载着字串又装载着整数,且建立後阵列...