Day2
对象
const声明的对象也可以被修改
(1)点取值和方括号取值的区别?
点取值在里面的键一般是没有引号的,比如:
let user = { name: "John", age: 30, // 这个就可以使用点进行取值 "likes birds": true // 多词属性名必须加引号,使用方括号进行取值 };
但是请注意方括号适用于所有情况,推荐使用方括号进行取值。
(2)计算属性
let fruit = prompt("Which fruit to buy?", "apple"); let bag = { [fruit]: 5, // 属性名是从 fruit 变量中得到的 }; alert( bag.apple ); // 5 如果 fruit="apple" // 实际上上述的方式与bag[fruit] = 5; 所实现的是一致的
(3)简写
function makeUser(name, age) { return { name, // 与 name: name 相同 age, // 与 age: age 相同 // ... }; }
当属性名与变量名一致时就可以使用对象的简写方式。
(4)属性存在性测试
JavaScript 的对象有一个需要注意的特性:能够被访问任何属性。即使属性不存在也不会报错!
语法:
"key" in object
(5)对象的循环
for xxx in xxx
这里循环的都是对象里面的键。
(6)删除属性
delete obj.prop
对象比较
(1)比较
请注意在js的对象是引用数据类型,因此其保存的是内存的地址,当两个对象变量比较的时候,需要看其引用的地址是否一致
let a = {}; let b = {}; // 两个独立的对象 alert( a == b ); // false //---------------------------- let a = {}; let b = a; // 复制引用 alert( a == b ); // true,都引用同一对象 alert( a === b ); // true
(2)如何克隆一个对象【引用的地址不相同】
使用Object.assign()方法
//Object.assign(dest, [src1, src2, src3...])语法 let user = { name: "John" }; let permissions1 = { canView: true }; let permissions2 = { canEdit: true }; // 将 permissions1 和 permissions2 中的所有属性都拷贝到 user 中 Object.assign(user, permissions1, permissions2); // 现在 user = { name: "John", canView: true, canEdit: true }
(3)深拷贝
内容:即对象里面还有对象,如果使用刚才的克隆方法,最后其里面的对象又可以彼此访问,不满足拷贝形式
// 例如 let user = { name: "John", sizes: { height: 182, width: 50 } }; let clone = Object.assign({}, user); alert( user.sizes === clone.sizes ); // true,同一个对象 // user 和 clone 分享同一个 sizes user.sizes.width++; // 通过其中一个改变属性值 alert(clone.sizes.width); // 51,能从另外一个获取到变更后的结果
解决办法:我们可以使用递归来实现它。或者为了不重复造轮子,采用现有的实现,例如 lodash 库的 _.cloneDeep(obj)。
垃圾回收机制
其实就是引用不到的数据,内存进行释。实现过程就是从全局的根(全局变量)开始沿着引用递归地进行标记,没标记的就被释放
优化:(1)分代收集【新老】 (2)增量收集【大小】 (3)闲时收集
this
this 总结:
--------------------
method 里的 this 返回的是 obj,
function 里的 this 非严格模式下返回的是 global or window ,严格模式下返回 undefined,
箭头函数里没有自己的 this
构造函数
理论上任何函数都可以做为构造函数(除去箭头函数)【因为要使用this】,一般的规则要求构造函数首字母大写,同时使用new关键字后传一个函数作为构造函数。
function Fun(str) { this.name = str this.a = false } let obj = new Fun("hello")
当一个函数被使用
new
操作符执行时,它按照以下步骤:
- 一个新的空对象被创建并分配给
this
。- 函数体执行。通常它会修改
this
,为其添加新的属性。- 返回
this
的值。
可选链
格式:
?.
这个其实与 ?? 运算符很像let user = null; alert( user.address ); // 直接报错,因为null不能有对应的属性 // 这样就不会报错了 let user = null; alert( user?.address ); // undefined alert( user?.address.street ); // undefined
Symbol
symbol
是唯一标识符的基本类型symbol 是使用带有可选描述(name)的
Symbol()
调用创建的。symbol 总是不同的值,即使它们有相同的名字。如果我们希望同名的 symbol 相等,那么我们应该使用全局注册表:
Symbol.for(key)
返回(如果需要的话则创建)一个以key
作为名字的全局 symbol。使用Symbol.for
多次调用key
相同的 symbol 时,返回的就是同一个 symbol。symbol 有两个主要的使用场景:
“隐藏” 对象属性。
如果我们想要向“属于”另一个脚本或者库的对象添加一个属性,我们可以创建一个 symbol 并使用它作为属性的键。symbol 属性不会出现在
for..in
中,因此它不会意外地被与其他属性一起处理。并且,它不会被直接访问,因为另一个脚本没有我们的 symbol。因此,该属性将受到保护,防止被意外使用或重写。因此我们可以使用 symbol 属性“秘密地”将一些东西隐藏到我们需要的对象中,但其他地方看不到它。
JavaScript 使用了许多系统 symbol,这些 symbol 可以作为
Symbol.*
访问。我们可以使用它们来改变一些内建行为。例如,在本教程的后面部分,我们将使用Symbol.iterator
来进行 迭代 操作,使用Symbol.toPrimitive
来设置 对象原始值的转换 等等。从技术上说,symbol 不是 100% 隐藏的。有一个内建方法 Object.getOwnPropertySymbols(obj) 允许我们获取所有的 symbol。还有一个名为 Reflect.ownKeys(obj) 的方法可以返回一个对象的 所有 键,包括 symbol。但大多数库、内建方法和语法结构都没有使用这些方法。
数据类型
数字
- 二进制 八进制 十六进制如何表示(0b, 0o, 0x)
- toString可以将数字转化为对应进制的字符串
- toFixed()保留小数点后几位[规则为四舍五入]
- isFinite()和isNaN()
isFinite(value)` 将其参数转换为数字,如果是常规数字而不是 `NaN/Infinity/-Infinity`,则返回 `true
parseInt(xxx,xxx)可以实现进制的字符串的转化
alert( parseInt('0xff', 16) ); // 255 alert( parseInt('ff', 16) ); // 255,没有 0x 仍然有效 alert( parseInt('2n9c', 36) ); // 123456
同时主要的还是转化为数字,但是规则不太一样
alert( parseInt('100px') ); // 100 alert( parseFloat('12.5em') ); // 12.5 alert( parseInt('12.3') ); // 12,只有整数部分被返回了 alert( parseFloat('12.3.4') ); // 12.3,在第二个点出停止了读取
- JS中小数除非是二进制可以表示的小数,其他小数都会有精度损失
数组
unshift、shift分别为开头添加与移除元素
且都支持多元素操作
关于length
当我们修改数组的时候,
length
属性会自动更新。准确来说,它实际上不是数组里元素的个数,而是最大的数字索引值加一。let fruits = []; fruits[123] = "Apple"; alert( fruits.length ); // 124 // -------------------------- let arr = [1, 2, 3, 4, 5]; arr.length = 2; // 截断到只剩 2 个元素 alert( arr ); // [1, 2] arr.length = 5; // 又把 length 加回来 alert( arr[3] ); // undefined:被截断的那些数值并没有回来 // 因此清空数组最简单的办法就是arr.length = 0
toString问题
数组没有
Symbol.toPrimitive
,也没有valueOf
,它们只能执行toString
进行转换,所以这里[]
就变成了一个空字符串,[1]
变成了"1"
,[1,2]
变成了"1,2"
alert( [] + 1 ); // "1" alert( [1] + 1 ); // "11" alert( [1,2] + 1 ); // "1,21"
万能函数 splice()
// 删除 let arr = ["I", "study", "JavaScript"]; arr.splice(1, 1); // 从索引 1 开始删除 1 个元素 alert( arr ); // ["I", "JavaScript"] // 删除并替换 let arr = ["I", "study", "JavaScript", "right", "now"]; // 删除数组的前三项,并使用其他内容代替它们 arr.splice(0, 3, "Let's", "dance"); alert( arr ) // 现在 ["Let's", "dance", "right", "now"] // 截取 let arr = ["I", "study", "JavaScript", "right", "now"]; // 删除前两个元素 let removed = arr.splice(0, 2); alert( removed ); // "I", "study" <-- 被从数组中删除了的元素 // 插入,就是将删除的参数设置为0即可 let arr = ["I", "study", "JavaScript"]; // 从索引 2 开始 // 删除 0 个元素 // 然后插入 "complex" 和 "language" arr.splice(2, 0, "complex", "language"); alert( arr ); // "I", "study", "complex", "language", "JavaScript"
截取slice
let arr = ["t", "e", "s", "t"]; alert( arr.slice(1, 3) ); // e,s(复制从位置 1 到位置 3 的元素) alert( arr.slice(-2) ); // s,t(复制从位置 -2 到尾端的元素)
拼接为 concat()
重点forEach(方法允许为数组的每个元素都运行一个函数。)
arr.forEach(function(item, index, array) { // ... do something with item }); // 例如 ["Bilbo", "Gandalf", "Nazgul"].forEach((item, index, array) => { alert(`${item} is at index ${index} in ${array}`); });
查找
方法
includes
可以正确的处理NaN
const arr = [NaN]; alert( arr.indexOf(NaN) ); // -1(错,应该为 0) alert( arr.includes(NaN) );// true(正确)
对象数组的处理arr.find方法与filter()方法
let result = arr.find(function(item, index, array) { // 如果返回 true,则返回 item 并停止迭代 // 对于假值(false)的情况,则返回 undefined });
例子 let users = [ {id: 1, name: "John"}, {id: 2, name: "Pete"}, {id: 3, name: "Mary"} ]; let user = users.find(item => item.id == 1); alert(user.name); // John
这里的find一般都是查找的单个元素,但如果有很多满足要求的元素就是用过filter函数进行实现
let users = [ {id: 1, name: "John"}, {id: 2, name: "Pete"}, {id: 3, name: "Mary"} ]; // 返回前两个用户的数组 let someUsers = users.filter(item => item.id < 3); alert(someUsers.length); // 2
map
arr.map 方法是最有用和经常使用的方法之一。
它对数组的每个元素都调用函数,并返回结果数组。
let result = arr.map(function(item, index, array) { // 返回新值而不是当前元素 }) let lengths = ["Bilbo", "Gandalf", "Nazgul"].map(item => item.length); alert(lengths); // 5,7,6 会把原来数组直接替换了
排序
arr.sort(fn)
方法实现了通用的排序算法。我们不需要关心它的内部工作原理(大多数情况下都是经过 快速排序 或 Timsort 算法优化的)。它将遍历数组,使用提供的函数比较其元素并对其重新排序,我们所需要的就是提供执行比较的函数fn
。function compareNumeric(a, b) { if (a > b) return 1; if (a == b) return 0; if (a < b) return -1; } let arr = [ 1, 2, 15 ]; arr.sort(compareNumeric); alert(arr); // 1, 2, 15
字符串与数组 拆分与合并
let names = 'Bilbo, Gandalf, Nazgul'; let arr = names.split(', '); for (let name of arr) { alert( `A message to ${name}.` ); // A message to Bilbo(和其他名字) } ------------------------ let arr = ['Bilbo', 'Gandalf', 'Nazgul']; let str = arr.join(';'); // 使用分号 ; 将数组粘合成字符串 alert( str ); // Bilbo;Gandalf;Nazgul
判断是否为数组
alert(Array.isArray({})); // false alert(Array.isArray([])); // true
Array.from()将可迭代对象转化为数组
Map
基本操作
let map = new Map(); map.set('1', 'str1'); // 字符串键 map.set(1, 'num1'); // 数字键 map.set(true, 'bool1'); // 布尔值键 // 还记得普通的 Object 吗? 它会将键转化为字符串 // Map 则会保留键的类型,所以下面这两个结果不同: alert( map.get(1) ); // 'num1' alert( map.get('1') ); // 'str1' alert( map.size ); // 3
遍历方式
let recipeMap = new Map([ ['cucumber', 500], ['tomatoes', 350], ['onion', 50] ]); // 遍历所有的键(vegetables) for (let vegetable of recipeMap.keys()) { alert(vegetable); // cucumber, tomatoes, onion } // 遍历所有的值(amounts) for (let amount of recipeMap.values()) { alert(amount); // 500, 350, 50 } // 遍历所有的实体 [key, value] for (let entry of recipeMap) { // 与 recipeMap.entries() 相同 alert(entry); // cucumber,500 (and so on) }
Set(不会重复)
其他类型
解构
解构赋值可以简洁地将一个对象或数组拆开赋值到多个变量上。
let user = { name: "John", age: 30 }; // 使用循环遍历键—值对 for (let [key, value] of Object.entries(user)) { alert(`${key}:${value}`); // name:John, then age:30 }
JSON的序列化与反序列化
JSON.stringify
将对象转换为 JSON。
JSON.parse
将 JSON 转换回对象。在stringify中还有一个参数可以确定那些key进行转化为字符串
let room = { number: 23 }; let meetup = { title: "Conference", participants: [{name: "John"}, {name: "Alice"}], place: room // meetup 引用了 room }; room.occupiedBy = meetup; // room 引用了 meetup alert( JSON.stringify(meetup, ['title', 'participants']) ); // {"title":"Conference","participants":[{},{}]} 这是因为name没有传进来,name也算是键值
同理在parse也可以传一个参数叫reviver
let str = '{"title":"Conference","date":"2017-11-30T12:00:00.000Z"}'; let meetup = JSON.parse(str, function(key, value) { if (key == 'date') return new Date(value); return value; }); alert( meetup.date.getDate() ); // 现在正常运行了!