[TOC]javascript
本部分主要是针对于JavaScript中经常使用的数据结构类型进行分析说明。css
在JavaScript中,基本的变量声明能够用var方式。JavaScript容许省略var,直接对未声明的变量赋值。也就是说,var a = 1 与 a = 1,这两条语句的效果相同。可是因为这样的作法很容易不知不觉地建立全局变量(尤为是在函数内部),因此建议老是使用var命令声明变量。html
在ES6中,对于变量声明的方式进行了扩展。引入了let方式,let便是定义块级别的变量,不会以闭包方式扩展到块外面。另外一种引入了能够用于定义常量的const声明。前端
function f() { { let x; { // okay, block scoped name const x = "sneaky"; // error, const x = "foo"; } // error, already declared in block let x = "inner"; } }
JavaScript是解释执行的语言, JavaScript引擎的工做方式是,先解析代码,获取全部被声明的变量,而后再一行一行地运行。这形成的结果,就是全部的变量的声明语句,都会被提高到代码的头部,这就叫作变量提高(hoisting)。java
console.log(a); var a = 1;
上面代码首先使用console.log方法,在控制台(console)显示变量a的值。这时变量a尚未声明和赋值,因此这是一种错误的作法,可是实际上不会报错。由于存在变量提高,真正运行的是下面的代码:node
var a; console.log(a); a = 1;
最后的结果是显示undefined,表示变量a已声明,但还未赋值。请注意,变量提高只对var命令声明的变量有效,若是一个变量不是用var命令声明的,就不会发生变量提高。git
console.log(b); b = 1;
上面的语句将会报错,提示“ReferenceError: b is not defined”,即变量b未声明,这是由于b不是用var命令声明的,JavaScript引擎不会将其提高,而只是视为对顶层对象的b属性的赋值。es6
在计算机编程中,全局变量指的是在全部做用域中都能访问的变量。全局变量是一种很差的实践,由于它会致使一些问题,好比一个已经存在的方法和全局变量的覆盖,当咱们不知道变量在哪里被定义的时候,代码就变得很难理解和维护了。在ES6中能够利用let
关键字来声明本地变量,好的 JavaScript 代码就是没有定义全局变量的。有一些技术能够帮助你让全部的事情都保持在本地:github
为了不全局变量,第一件事情就是要确保全部的代码都被包在函数中。最简单的办法就是把全部的代码都直接放到一个函数中去:web
(function(win) { "use strict"; // 进一步避免建立全局变量 var doc = window.document; // 在这里声明你的变量 // 一些其余的代码 }(window));
var MyApp = { namespace: function(ns) { var parts = ns.split("."), object = this, i, len; for(i = 0, len = parts.lenght; i < len; i ++) { if(!object[parts[i]]) { object[parts[i]] = {}; } object = object[parts[i]]; } return object; } }; // 定义命名空间 MyApp.namespace("Helpers.Parsing"); // 你如今可使用该命名空间了 MyApp.Helpers.Parsing.DateParser = function() { //作一些事情 };
另外一项开发者用来避免全局变量的技术就是封装到模块 Module
中。一个模块就是不须要建立新的全局变量或者命名空间的通用的功能。不要将全部的代码都放一个负责执行任务或者发布接口的函数中。最多见的 JavaScript 模块类型就是异步模块定义 Asynchronous Module Definition (AMD)
。
//定义 define( "parsing", //模块名字 [ "dependency1", "dependency2" ], // 模块依赖 function( dependency1, dependency2) { //工厂方法 // Instead of creating a namespace AMD modules // are expected to return their public interface var Parsing = {}; Parsing.DateParser = function() { //do something }; return Parsing; } ); // 经过 Require.js 加载模块 require(["parsing"], function(Parsing) { Parsing.DateParser(); // 使用模块 });
解构赋值容许你使用相似数组或对象字面量的语法将数组和对象的属性赋给各类变量。这种赋值语法极度简洁,同时还比传统的属性访问方法更为清晰。
传统的访问数组前三个元素的方式为:
var first = someArray[0]; var second = someArray[1]; var third = someArray[2];
而经过解构赋值的特性,能够变为:
var [first, second, third] = someArray;
以上是数组解构赋值的一个简单示例,其语法的通常形式为:
[ variable1, variable2, ..., variableN ] = array;
这将为variable1到variableN的变量赋予数组中相应元素项的值。若是你想在赋值的同时声明变量,可在赋值语句前加入var
、let
或const
关键字,例如:
var [ variable1, variable2, ..., variableN ] = array; let [ variable1, variable2, ..., variableN ] = array; const [ variable1, variable2, ..., variableN ] = array;
事实上,用变量
来描述并不恰当,由于你能够对任意深度的嵌套数组进行解构:
var [foo, [[bar], baz]] = [1, [[2], 3]]; console.log(foo); // 1 console.log(bar); // 2 console.log(baz); // 3
此外,你能够在对应位留空来跳过被解构数组中的某些元素:
var [,,third] = ["foo", "bar", "baz"]; console.log(third); // "baz"
并且你还能够经过“不定参数”模式捕获数组中的全部尾随元素:
var [head, ...tail] = [1, 2, 3, 4]; console.log(tail); // [2, 3, 4]
当访问空数组或越界访问数组时,对其解构与对其索引的行为一致,最终获得的结果都是:undefined
。
console.log([][0]); // undefined var [missing] = []; console.log(missing); // undefined
请注意,数组解构赋值的模式一样适用于任意迭代器:
function* fibs() { var a = 0; var b = 1; while (true) { yield a; [a, b] = [b, a + b]; } } var [first, second, third, fourth, fifth, sixth] = fibs(); console.log(sixth); // 5
经过解构对象,你能够把它的每一个属性与不一样的变量绑定,首先指定被绑定的属性,而后紧跟一个要解构的变量。
var robotA = { name: "Bender" }; var robotB = { name: "Flexo" }; var { name: nameA } = robotA; var { name: nameB } = robotB; console.log(nameA); // "Bender" console.log(nameB); // "Flexo"
当属性名与变量名一致时,能够经过一种实用的句法简写:
var { foo, bar } = { foo: "lorem", bar: "ipsum" }; console.log(foo); // "lorem" console.log(bar); // "ipsum"
与数组解构同样,你能够随意嵌套并进一步组合对象解构:
var complicatedObj = { arrayProp: [ "Zapp", { second: "Brannigan" } ] }; var { arrayProp: [first, { second }] } = complicatedObj; console.log(first); // "Zapp" console.log(second); // "Brannigan"
当你解构一个未定义的属性时,获得的值为undefined
:
var { missing } = {}; console.log(missing); // undefined
请注意,当你解构对象并赋值给变量时,若是你已经声明或不打算声明这些变量(亦即赋值语句前没有let
、const
或var
关键字),你应该注意这样一个潜在的语法错误:
{ blowUp } = { blowUp: 10 }; // Syntax error 语法错误
为何会出错?这是由于JavaScript语法通知解析引擎将任何以{开始的语句解析为一个块语句(例如,{console}
是一个合法块语句)。解决方案是将整个表达式用一对小括号包裹:
({ safe } = {}); // No errors 没有语法错误
当你要解构的属性未定义时你能够提供一个默认值:
var [missing = true] = []; console.log(missing); // true var { message: msg = "Something went wrong" } = {}; console.log(msg); // "Something went wrong" var { x = 3 } = {}; console.log(x); // 3
因为解构中容许对对象进行解构,而且还支持默认值,那么彻底能够将解构应用在函数参数以及参数的默认值中。
function removeBreakpoint({ url, line, column }) { // ... }
当咱们构造一个提供配置的对象,而且须要这个对象的属性携带默认值时,解构特性就派上用场了。举个例子,jQuery的ajax
函数使用一个配置对象做为它的第二参数,咱们能够这样重写函数定义:
jQuery.ajax = function (url, { async = true, beforeSend = noop, cache = true, complete = noop, crossDomain = false, global = true, // ... 更多配置 }) { // ... do stuff };
一样,解构也能够应用在函数的多重返回值中,能够相似于其余语言中的元组的特性:
function returnMultipleValues() { return [1, 2]; } var [foo, bar] = returnMultipleValues();
function myFunction(x, y, z) { } var args = [0, 1, 2]; myFunction(...args);
还有另一个好处就是能够用来替换Object.assign来方便地从旧有的对象中建立新的对象,而且可以修改部分值。譬如
var obj = {a:1,b:2} var obj_new_1 = Object.assign({},obj,{a:3}); var obj_new_2 = { ...obj, a:3 }
typeof运算符能够返回一个值的数据类型,可能有如下结果。
(1)原始类型
数值、字符串、布尔值分别返回number、string、boolean。
typeof 123 // "number" typeof "123" // "string" typeof false // "boolean"
(2)函数
函数返回function。
// 定义一个空函数 function f(){} typeof f // "function"
(3)undefined
undefined返回undefined。
typeof undefined // "undefined"
利用这一点,typeof能够用来检查一个没有声明的变量,而不报错。
v // ReferenceError: v is not defined typeof v // "undefined"
实际编程中,这个特色一般用在判断语句。
// 错误的写法 if (v){ // ... } // ReferenceError: v is not defined // 正确的写法 if (typeof v === "undefined"){ // ... }
(4)其余
除此之外,都返回object。
typeof window // "object" typeof {} // "object" typeof [] // "object" typeof null // "object"
从上面代码能够看到,空数组([])的类型也是object,这表示在JavaScript内部,数组本质上只是一种特殊的对象。另外,null的类型也是object,这是因为历史缘由形成的,为了兼容之前的代码,后来就无法修改了,并非说null就属于对象,本质上null是一个相似于undefined的特殊值。
typeof对数组(array)和对象(object)的显示结果都是object,那么怎么区分它们呢?instanceof运算符能够作到。
var o = {}; var a = []; o instanceof Array // false a instanceof Array // true
当遇到如下几种状况,JavaScript会自动转换数据类型:
不一样类型的数据进行互相运算;
对非布尔值类型的数据求布尔值;
对非数值类型的数据使用一元运算符(即“+”和“-”)。
random() 方法可返回介于 0 ~ 1 之间的一个随机数。
<script type="text/javascript"> document.write(Math.random()) </script>
使用Number函数,能够将任意类型的值转化成数字。
数值:转换后仍是原来的值。
字符串:若是能够被解析为数值,则转换为相应的数值,不然获得NaN。空字符串转为0。
布尔值:true转成1,false转成0。
undefined:转成NaN。
null:转成0。
Number函数将字符串转为数值,要比parseInt函数严格不少。基本上,只要有一个字符没法转成数值,整个字符串就会被转为NaN。
parseInt('011') // 9 parseInt('42 cats') // 42 parseInt('0xcafebabe') // 3405691582 Number('011') // 11 Number('42 cats') // NaN Number('0xcafebabe') // 3405691582
若是Number传入的参数是一个对象,那么转换规则会相对复杂一点,具体而言描述以下:
先调用对象自身的valueOf方法,若是该方法返回原始类型的值(数值、字符串和布尔值),则直接对该值使用Number方法,再也不进行后续步骤。
若是valueOf方法返回复合类型的值,再调用对象自身的toString方法,若是toString方法返回原始类型的值,则对该值使用Number方法,再也不进行后续步骤。
若是toString方法返回的是复合类型的值,则报错。
这里用的是numeraljs,格式化显示的效果以下所示:
Number
Number | Format | String |
---|---|---|
10000 | '0,0.0000' | 10,000.0000 |
10000.23 | '0,0' | 10,000 |
10000.23 | '+0,0' | +10,000 |
Currency
Number | Format | String |
---|---|---|
1000.234 | '$0,0.00' | $1,000.23 |
1000.2 | '0,0[.]00 $' | 1,000.20 $ |
Bytes
Number | Format | String |
---|---|---|
100 | '0b' | 100B |
2048 | '0 b' | 2 KB |
Percentages
Number | Format | String |
---|---|---|
1 | '0%' | 100% |
0.974878234 | '0.000%' | 97.488% |
Time
Number | Format | String |
---|---|---|
25 | '00:00:00' | 0:00:25 |
238 | '00:00:00' | 0:03:58 |
布尔值表明“真”和“假”两个状态。“真”用关键字true表示,“假”用关键字false表示。布尔值只有这两个值。若是JavaScript预期某个位置应该是布尔值,会将该位置上现有的值自动转为布尔值。转换规则是除了下面六个值被转为false,其余值都视为true。
undefined
null
false
0
NaN
""(空字符串)
全部对象的布尔值都是true,甚至连false对应的布尔对象也是true。
Boolean(new Boolean(false)) // true
JavaScript中常见的空类型为undefined与null,不过typeof undefined === ‘undefined’
而 typeof null === ‘object’
。
null表示"没有对象",即该处不该该有值。典型用法是:
做为函数的参数,表示该函数的参数是对象。
做为对象原型链的终点。
undefined表示"缺乏值",就是此处应该有一个值,可是还未定义。典型用法是:
变量被声明了,但没有赋值时,就等于undefined。
调用函数时,应该提供的参数没有提供,该参数等于undefined。
对象没有赋值的属性,该属性的值为undefined。
函数没有返回值时,默认返回undefined。
Symbols是JavaScript的第七种原始类型,它代指一个全局惟一的不可变对象。若是须要建立一个Symbol对象,则须要调用Symbol函数:
var sym1 = Symbol(); var sym2 = Symbol("foo"); var sym3 = Symbol("foo");
如上的代码会建立三个新的符号,注意,虽然Symbol使用了”foo”这个字符串做为输入对象,可是每次会建立一个新的符号:
Symbol("foo") === Symbol("foo"); // false
确切地说,symbol与其它类型并不彻底相像。symbol被建立后就不可变动,你不能为它设置属性(在严格模式下尝试设置属性会获得TypeError的错误)。他们能够用做属性名称,这些性质与字符串相似。
另外一方面,每个symbol都独一无二,不与其它symbol等同,即便两者有相同的描述也不相等;你能够轻松地建立一个新的symbol。这些性质与对象相似。
ES6中的symbol与Lisp和Ruby这些语言中更传统的symbol相似,但不像它们集成得那么紧密。在Lisp中,全部的标识符都是symbol;在JS中,标识符和大多数的属性键仍然是字符串,symbol只是一个额外的选项。
关于symbol的忠告:symbol不能被自动转换为字符串,这和语言中的其它类型不一样。尝试拼接symbol与字符串将获得TypeError错误。
> var sym = Symbol("<3"); > "your symbol is " + sym // TypeError: can't convert symbol to string > `your symbol is ${sym}` // TypeError: can't convert symbol to string
有三种获取symbol的方法。
调用Symbol()。正如咱们上文中所讨论的,这种方式每次调用都会返回一个新的惟一symbol。
调用Symbol.for(string)。这种方式会访问symbol注册表,其中存储了已经存在的一系列symbol。这种方式与经过Symbol()
定义的独立symbol不一样,symbol注册表中的symbol是共享的。若是你连续三十次调用Symbol.for("cat")
,每次都会返回相同的symbol。注册表很是有用,在多个web页面或同一个web页面的多个模块中常常须要共享一个symbol。
使用标准定义的symbol,例如:Symbol.iterator。标准根据一些特殊用途定义了少量的几个symbol。
ES6中开始支持较为复杂的模板字符串方式:
// Basic literal string creation `In JavaScript '\n' is a line-feed.` // Multiline strings `In JavaScript this is not legal.` // String interpolation var name = "Bob", time = "today"; `Hello ${name}, how are you ${time}?` // Construct an HTTP request prefix is used to interpret the replacements and construction GET`http://foo.org/bar?a=${a}&b=${b} Content-Type: application/json X-Credentials: ${credentials} { "foo": ${foo}, "bar": ${bar}}`(myOnReadyStateChangeHandler);
使用String函数,能够将任意类型的值转化成字符串。规则以下:
数值:转为相应的字符串。
字符串:转换后仍是原来的值。
布尔值:true转为“true”,false转为“false”。
undefined:转为“undefined”。
null:转为“null”。
而对于较复杂一点的对象类型,转换规则以下:
先调用toString方法,若是toString方法返回的是原始类型的值,则对该值使用String方法,再也不进行如下步骤。
若是toString方法返回的是复合类型的值,再调用valueOf方法,若是valueOf方法返回的是原始类型的值,则对该值使用String方法,再也不进行如下步骤。
若是valueOf方法返回的是复合类型的值,则报错。
function html_encode(str) { var s = ""; if (str.length == 0) return ""; s = str.replace(/&/g, ">"); s = s.replace(/</g, "<"); s = s.replace(/>/g, ">"); s = s.replace(/ /g, " "); s = s.replace(/\'/g, "'"); s = s.replace(/\"/g, """); s = s.replace(/\n/g, "<br>"); return s; } function html_decode(str) { var s = ""; if (str.length == 0) return ""; s = str.replace(/>/g, "&"); s = s.replace(/</g, "<"); s = s.replace(/>/g, ">"); s = s.replace(/ /g, " "); s = s.replace(/'/g, "\'"); s = s.replace(/"/g, "\""); s = s.replace(/<br>/g, "\n"); return s; }
str.split('').reverse().join('');
根据毫秒计算对应的天/时/分/秒
var left_time = <!--{$left_time}-->; function GetRTime(){ //var NowTime = new Date(); <!--获取当前时间,并使用当前时间减去开始时间--> //var nMS = startTime.getTime() - NowTime.getTime() left_time = left_time - 1000; var nMS = left_time; var nD = Math.floor(nMS/(1000 * 60 * 60 * 24)); var nH = Math.floor(nMS/(1000*60*60)) % 24; var nM = Math.floor(nMS/(1000*60)) % 60; var nS = Math.floor(nMS/1000) % 60; if (nMS < 0){ }else{ } }
moment().format('MMMM Do YYYY, h:mm:ss a'); // August 26th 2015, 9:13:04 pm moment().format('dddd'); // Wednesday moment().format("MMM Do YY"); // Aug 26th 15 moment().format('YYYY [escaped] YYYY'); // 2015 escaped 2015 moment().format(); // 2015-08-26T21:13:04+08:00 var dateString = moment.unix(value).format("MM/DD/YYYY");
若是须要显示本地化时间,须要引入locale目录下对应文件,而且使用moment.locale(String);
进行设置。另外注意,Unix时间戳指的是
var a = moment([2007, 0, 29]); var b = moment([2007, 0, 28]); a.diff(b) // 86400000 milliseconds a.diff(b, 'days') // 1
若是须要将duration格式化为较好的显示,能够参考moment-duration-format。
在JavaScript中,Array是一个全局的对象能够用来建立数组,是一个高级别的有点相似于列表的集合。能够直接使用Array.length
来获取某个数组的长度。
JavaScript建立新的数组,能够采用以下方式:
arr = [] arr = new Array() arr = new Array([PreSize]) arr = [] arr[1] = 1 // arr = [undefined , 1] 对于不存在的自动赋值为undefined
slice() 方法把数组中一部分的浅复制(shallow copy)存入一个新的数组对象中,并返回这个新的数组。array.slice(begin[, end]).
slice会提取原数组中索引从 begin 到 end 的全部元素(包含begin,但不包含end)。
slice(1,4) 提取原数组中的第二个元素开始直到第四个元素的全部元素 (索引为 1, 2, 3的元素)。
例子:返回数组中的一部分
// Our good friend the citrus from fruits example var fruits = ["Banana", "Orange", "Lemon", "Apple", "Mango"]; var citrus = fruits.slice(1, 3); // puts --> ["Orange","Lemon"]
例子:使用 slice
在下例中, slice从
`myCar中建立了一个新数组`newCar
.两个数组都包含了一个myHonda
对象的引用. 当myHonda
的color属性改变为purple, 则两个数组中的对应元素都会随之改变.
// 使用slice方法从myCar中建立一个newCar. var myHonda = { color: "red", wheels: 4, engine: { cylinders: 4, size: 2.2 } }; var myCar = [myHonda, 2, "cherry condition", "purchased 1997"]; var newCar = myCar.slice(0, 2); // 输出myCar, newCar,以及各自的myHonda对象引用的color属性. print("myCar = " + myCar.toSource()); print("newCar = " + newCar.toSource()); print("myCar[0].color = " + myCar[0].color); print("newCar[0].color = " + newCar[0].color); // 改变myHonda对象的color属性. myHonda.color = "purple"; print("The new color of my Honda is " + myHonda.color); //输出myCar, newCar中各自的myHonda对象引用的color属性. print("myCar[0].color = " + myCar[0].color); print("newCar[0].color = " + newCar[0].color);
上述代码输出:
myCar = [{color:"red", wheels:4, engine:{cylinders:4, size:2.2}}, 2, "cherry condition", "purchased 1997"] newCar = [{color:"red", wheels:4, engine:{cylinders:4, size:2.2}}, 2] myCar[0].color = red newCar[0].color = red The new color of my Honda is purple myCar[0].color = purple newCar[0].color = purple
类数组(Array-like)对象
slice
方法能够用来将一个类数组(Array-like)对象/集合转换成一个数组。你只需将该方法绑定到这个对象上。下述代码中 list 函数中的 arguments
就是一个类数组对象。
function list() { return Array.prototype.slice.call(arguments); } var list1 = list(1, 2, 3); // [1, 2, 3]
除了使用 Array.prototype.slice.call(arguments)
,你也能够简单的使用 [].slice.call(arguments)
来代替。另外,你可使用 bind
来简化该过程。
var unboundSlice = Array.prototype.slice; var slice = Function.prototype.call.bind(unboundSlice); function list() { return slice(arguments); } var list1 = list(1, 2, 3); // [1, 2, 3]
JavaScript的Array支持从头部插入移除,从尾部插入移除等多种方式:
var fruits = ["Apple", "Banana"]; var newLength = fruits.push("Orange");//从尾部插入,返回的数组长度 // ["Apple", "Banana", "Orange"] var last = fruits.pop(); // remove Orange (from the end),返回删除的对象 // ["Apple", "Banana"]; var first = fruits.shift(); // remove Apple from the front // ["Banana"]; var newLength = fruits.unshift("Strawberry") // add to the front // ["Strawberry", "Banana"];
var array = [1, 2, 3]; array.includes(1); // → true
若是在数组中须要反向索引某个元素的位置,可使用indexOf
方法
fruits.push("Mango"); // ["Strawberry", "Banana", "Mango"] var pos = fruits.indexOf("Banana"); // 1 var array = [2, 9, 9, 4, 3, 6]; var result = array.lastIndexOf(9); console.log(result); // output: 2
若是存储的是复杂的对象,则可使用find
方法,返回数组中知足测试条件的一个元素,若是没有知足条件的元素,则返回 undefined。
_.find(users, function(o) { return o.age < 40; }); // output: object for 'barney' // Native var users = [ { 'user': 'barney', 'age': 36, 'active': true }, { 'user': 'fred', 'age': 40, 'active': false }, { 'user': 'pebbles', 'age': 1, 'active': true } ]; users.find(function(o) { return o.age < 40; }); // output: object for 'barney' var index = users.findIndex(function(o) { return o.age >= 40; }); console.log(index); // output: 1
fruits.forEach(function (item, index, array) { console.log(item, index); }); // Apple 0 // Banana 1
集合也是一种常见的数据结构,在ES6中添加了对于集合的支持,其基本用法以下:
// Sets var s = new Set(); s.add("hello").add("goodbye").add("hello"); s.size === 2; s.has("hello") === true;
WeakSet提供了一种自回收的存储方式:
// Weak Sets var ws = new WeakSet(); ws.add({ data: 42 }); // Because the added object has no other references, it will not be held in the set
将某个列表中的元素映射到新的列表中。
// Native var array1 = [1, 2, 3]; var array2 = array1.map(function(value, index) { return value*2; }); console.log(array2); // output: [2, 4, 6]
var array = [0, 1, 2, 3, 4]; var result = array.reduce(function (previousValue, currentValue, currentIndex, array) { return previousValue + currentValue; }); console.log(result); // output: 10 //reduceRight,正好方向相反 var array = [0, 1, 2, 3, 4]; var result = array.reduceRight(function (previousValue, currentValue, currentIndex, array) { return previousValue - currentValue; }); console.log(result); // output: -2
// Native function isBigEnough(value) { return value >= 10; } var array = [12, 5, 8, 130, 44]; var filtered = array.filter(isBigEnough); console.log(filtered); // output: [12, 130, 44]
JavaScript中Object是一个混合了相似于Dictionary与Class的用法,基本上来讲也是一种键值类型。其中键的类型主要包含三种:
Identifier:包含任何有效地标识符,包括了ES的保留关键字。
字符串:single ('
) or double ("
) quotes. 'foo'
, "bar"
,'qu\'ux'
, ""
(the empty string), and 'Ich \u2665 B\xFCcher'
are all valid string literals.
数字:decimal literal (e.g. 0
, 123
, 123.
, .123
, 1.23
, 1e23
, 1E-23
, 1e+23
, 12
, but not 01
, +123
or -123
) or a hex integer literal (0[xX][0-9a-fA-F]+
in regex, e.g. 0xFFFF
, 0X123
,0xaBcD
).
var object = { // `abc` is a valid identifier; no quotes are needed abc: 1, // `123` is a numeric literal; no quotes are needed 123: 2, // `012` is an octal literal with value `10` and thus isn’t allowed in strict mode; but if you insist on using it, quotes aren’t needed 012: 3, // `π` is a valid identifier; no quotes are needed π: Math.PI, // `var` is a valid identifier name (although it’s a reserved word); no quotes are needed var: 4, // `foo bar` is not a valid identifier name; quotes are required 'foo bar': 5, // `foo-bar` is not a valid identifier name; quotes are required 'foo-bar': 6, // the empty string is not a valid identifier name; quotes are required '': 7 };
注意,与object不一样的是,JSON 只容许用双引号 ("
) 包裹的字符串做为键名。而若是要根据键名进行索引的话,可使用方括号,这种方式对于三种键值皆有效:
object['abc']; // 1
有时候也可使用点操做符,不过这种方式只能够被用于键为有效地Identifier状况:
object.abc; // 1
若是须要获取全部的键名的话,可使用Object.keys方法:
注意,全部的Object的方法只能用Object.methodName方式调用
// Native var result2 = Object.keys({one: 1, two: 2, three: 3}); console.log(result2); // output: ["one", "two", "three"] //也能够等效获取大小 var result2 = Object.keys({one: 1, two: 2, three: 3}).length; console.log(result2); // output: 3
其基本语法为:
Object.create(proto, { propertiesObject })
这里须要注意的是,propertiesObject不是一个简单的键值类型,而是有固定格式的object。
var o; // 建立一个原型为null的空对象 o = Object.create(null); o = {}; // 以字面量方式建立的空对象就至关于: o = Object.create(Object.prototype); o = Object.create(Object.prototype, { // foo会成为所建立对象的数据属性 foo: { writable:true, configurable:true, value: "hello" }, // bar会成为所建立对象的访问器属性 bar: { configurable: false, get: function() { return 10 }, set: function(value) { console.log("Setting `o.bar` to", value) } }}) function Constructor(){} o = new Constructor(); // 上面的一句就至关于: o = Object.create(Constructor.prototype); // 固然,若是在Constructor函数中有一些初始化代码,Object.create不能执行那些代码 // 建立一个以另外一个空对象为原型,且拥有一个属性p的对象 o = Object.create({}, { p: { value: 42 } }) // 省略了的属性特性默认为false,因此属性p是不可写,不可枚举,不可配置的: o.p = 24 o.p //42 o.q = 12 for (var prop in o) { console.log(prop) } //"q" delete o.p //false //建立一个可写的,可枚举的,可配置的属性p o2 = Object.create({}, { p: { value: 42, writable: true, enumerable: true, configurable: true } });
Object.assign
:一层浅复制Object.assign() 方法能够把任意多个的源对象所拥有的自身可枚举属性拷贝给目标对象,而后返回目标对象。Object.assign
方法只会拷贝源对象自身的而且可枚举的属性到目标对象身上。注意,对于访问器属性,该方法会执行那个访问器属性的 getter
函数,而后把获得的值拷贝给目标对象,若是你想拷贝访问器属性自己,请使用 Object.getOwnPropertyDescriptor()
和Object.defineProperties()
方法。
注意,在属性拷贝过程当中可能会产生异常,好比目标对象的某个只读属性和源对象的某个属性同名,这时该方法会抛出一个 TypeError
异常,拷贝过程当中断,已经拷贝成功的属性不会受到影响,还未拷贝的属性将不会再被拷贝。
注意, Object.assign
会跳过那些值为 null
或 undefined
的源对象。
Object.assign(target, ...sources)
例子:浅拷贝一个对象
var obj = { a: 1 }; var copy = Object.assign({}, obj); console.log(copy); // { a: 1 }
例子:合并若干个对象
var o1 = { a: 1 }; var o2 = { b: 2 }; var o3 = { c: 3 }; var obj = Object.assign(o1, o2, o3); console.log(obj); // { a: 1, b: 2, c: 3 } console.log(o1); // { a: 1, b: 2, c: 3 }, 注意目标对象自身也会改变。
例子:拷贝 symbol 类型的属性
var o1 = { a: 1 }; var o2 = { [Symbol("foo")]: 2 }; var obj = Object.assign({}, o1, o2); console.log(obj); // { a: 1, [Symbol("foo")]: 2 }
例子:继承属性和不可枚举属性是不能拷贝的
var obj = Object.create({foo: 1}, { // foo 是个继承属性。 bar: { value: 2 // bar 是个不可枚举属性。 }, baz: { value: 3, enumerable: true // baz 是个自身可枚举属性。 } }); var copy = Object.assign({}, obj); console.log(copy); // { baz: 3 }
例子:原始值会被隐式转换成其包装对象
var v1 = "123"; var v2 = true; var v3 = 10; var v4 = Symbol("foo") var obj = Object.assign({}, v1, null, v2, undefined, v3, v4); // 源对象若是是原始值,会被自动转换成它们的包装对象, // 而 null 和 undefined 这两种原始值会被彻底忽略。 // 注意,只有字符串的包装对象才有可能有自身可枚举属性。 console.log(obj); // { "0": "1", "1": "2", "2": "3" }
例子:拷贝属性过程当中发生异常
var target = Object.defineProperty({}, "foo", { value: 1, writeable: false }); // target 的 foo 属性是个只读属性。 Object.assign(target, {bar: 2}, {foo2: 3, foo: 3, foo3: 3}, {baz: 4}); // TypeError: "foo" is read-only // 注意这个异常是在拷贝第二个源对象的第二个属性时发生的。 console.log(target.bar); // 2,说明第一个源对象拷贝成功了。 console.log(target.foo2); // 3,说明第二个源对象的第一个属性也拷贝成功了。 console.log(target.foo); // 1,只读属性不能被覆盖,因此第二个源对象的第二个属性拷贝失败了。 console.log(target.foo3); // undefined,异常以后 assign 方法就退出了,第三个属性是不会被拷贝到的。 console.log(target.baz); // undefined,第三个源对象更是不会被拷贝到的。
不过须要注意的是,assign是浅拷贝,或者说,它是一级深拷贝,举两个例子说明:
const defaultOpt = { title: { text: 'hello world', subtext: 'It\'s my world.' } }; const opt = Object.assign({}, defaultOpt, { title: { subtext: 'Yes, your world.' } }); console.log(opt); // 预期结果 { title: { text: 'hello world', subtext: 'Yes, your world.' } } // 实际结果 { title: { subtext: 'Yes, your world.' } }
上面这个例子中,对于对象的一级子元素而言,只会替换引用,而不会动态的添加内容。那么,其实assign并无解决对象的引用混乱问题,参考下下面这个例子:
const defaultOpt = { title: { text: 'hello world', subtext: 'It\'s my world.' } }; const opt1 = Object.assign({}, defaultOpt); const opt2 = Object.assign({}, defaultOpt); opt2.title.subtext = 'Yes, your world.'; console.log('opt1:'); console.log(opt1); console.log('opt2:'); console.log(opt2); // 结果 opt1: { title: { text: 'hello world', subtext: 'Yes, your world.' } } opt2: { title: { text: 'hello world', subtext: 'Yes, your world.' } }
var map = new Map(); map.set("foo", "bar"); console.log(map.get("foo")); //logs "bar" var animalSounds = new Map(); animalSounds.set("dog", "woof"); animalSounds.set("cat", "meow"); animalSounds.set("frog", "ribbit"); console.log(animalSounds.size); //logs 3 console.log(animalSounds.has("dog")); //logs true animalSounds.delete("dog"); console.log(animalSounds.size); //logs 2 console.log(animalSounds.has("dog")); //logs false animalSounds.clear(); console.log(animalSounds.size); //logs 0
usersMap = new Map(); usersMap.set(1, "sally"); usersMap.set(2, "bob"); usersMap.set(3, "jane"); console.log(usersMap.get(1)); //logs "sally" usersMap.forEach(function (username, userId) { console.log(userId, typeof userId); //logs 1..3, "number" if (userId === 1) { console.log("We found sally."); } }); //若是用for...of方式遍历,每次返回的是一个Array for (data of usersMap) { console.log(data);//Array [1,"sally"] }
Map的键的类型能够是object、NaN等等。
var obj, map; map = new Map(); obj = {foo: "bar"}; map.set(obj, "foobar"); obj.newProp = "stuff"; console.log(map.has(obj)); //logs true console.log(map.get(obj)); //logs "foobar"
FaceBook-Immutable
Immutable 详解及 React 中实践
Immutable 对象一旦被建立以后即不可再更改,这样可使得应用开发工做变得简化,再也不须要大量的保护性拷贝,使用简单的逻辑控制便可以保证内存控制与变化检测。Immutable.js虽然和React同期出现且跟React配合很爽,但它可不是React工具集里的(它的光芒被掩盖了),它是一个彻底独立的库,不管基于什么框架均可以用它。意义在于它弥补了Javascript没有不可变数据结构的问题。不可变数据结构是函数式编程中必备的。前端工程师被OOP洗脑过久了,组件根本上就是函数用法,FP的特色更适用于前端开发。
Javascript中对象都是参考类型,也就是a={a:1}; b=a; b.a=10;
你发现a.a
也变成10了。可变的好处是节省内存或是利用可变性作一些事情,可是,在复杂的开发中它的反作用远比好处大的多。因而才有了浅copy和深copy,就是为了解决这个问题。举个常见例子:
var defaultConfig = { /* 默认值 */}; var config = $.extend({}, defaultConfig, initConfig); // jQuery用法。initConfig是自定义值 var config = $.extend(true, {}, defaultConfig, initConfig); // 若是对象是多层的,就用到deep-copy了
而
var stateV1 = Immutable.fromJS({ users: [ { name: 'Foo' }, { name: 'Bar' } ] }); var stateV2 = stateV1.updateIn(['users', 1], function () { return Immutable.fromJS({ name: 'Barbar' }); }); stateV1 === stateV2; // false stateV1.getIn(['users', 0]) === stateV2.getIn(['users', 0]); // true stateV1.getIn(['users', 1]) === stateV2.getIn(['users', 1]); // false
如上,咱们可使用===来经过引用来比较对象,这意味着咱们可以方便快速的进行对象比较,而且它可以和React中的PureRenderMixin 兼容。基于此,咱们能够在整个应用构建中使用Immutable.js。也就是说,咱们的Flux Store应该是一个具备不变性的对象,而且咱们经过 将具备不变性的数据做为属性传递给咱们的应用程序。
若是要建立Immutable对象,使用fromJS
方法既能够将简单的JS中的objects与arrays转化为不可变的Maps与Lists
fromJS(json: any, reviver?: (k: any, v: Iterable<any, any>) => any): any
若是reviver
这个属性被提供了,那么它会传入一个Seq对象而且被循环调用,对于顶层对象,它的默认的键为""
。
Immutable.fromJS({a: {b: [10, 20, 30]}, c: 40}, function (key, value) { var isIndexed = Immutable.Iterable.isIndexed(value); return isIndexed ? value.toList() : value.toOrderedMap(); }); // true, "b", {b: [10, 20, 30]} // false, "a", {a: {b: [10, 20, 30]}, c: 40} // false, "", {"": {a: {b: [10, 20, 30]}, c: 40}}
对于转化而来的Immutable对象,能够经过Iterable.is*
方法来判断其是列表仍是映射或者其余数据类型。
要更新Map中的某个元素值,须要调用updateIn方法,该方法会根据传入的keyPath寻找到该值,而后进行安全修正并返回一个新的对象。若是keyPath值并不存在,那么返回的Map对象会自动建立该键,若是KeyPath没有提供指定值,那么会自动调用notSetValue
或者赋值为undefined
updateIn(keyPath: Array<any>, updater: (value: any) => any): Map<K, V> updateIn( keyPath: Array<any>, notSetValue: any, updater: (value: any) => any ): Map<K, V> updateIn(keyPath: Iterable<any, any>, updater: (value: any) => any): Map<K, V> updateIn( keyPath: Iterable<any, any>, notSetValue: any, updater: (value: any) => any ): Map<K, V>
var data = Immutable.fromJS({ a: { b: { c: 10 } } }); data = data.updateIn(['a', 'b', 'c'], val => val * 2); // { a: { b: { c: 20 } } }