目录
- 1. Array.prototype.copyWithin(ES6)
- 2. generator与iterator(ES6)
- 3. Array.prototype.entries(ES6)
- 4. Array.prototype.fill(ES6)
- 5. Array.prototype.find(ES6)
- 6. Array.prototype.findIndex(ES6)
- 7. Array.prototype.flat(ES7)
- 8. Array.prototype.flatMap(ES7)
- 9. Array.from(ES6)
- 10. 相等性判断与Object.is方法(ES6)
- 11. Array.prototype.includes(ES7)
- 12. Array.prototype.sort(ES3)
1. Array.prototype.copyWithin(ES6)
-
概念
复制数组的一部分到同一数组中的另一个位置,返回原数组,不会改变原数组的长度
原理:复制元素集合 => 全选模板元素 => 粘贴
-
写法
arr.copyWithin(target[, start[, end]])
target:将复制的粘贴到索引位置
start:开始复制元素的起始位置
end:开始复制元素的结束位置
const arr = [1, 2, 3, 4, 5]; const newArr = arr.copyWithin(0, 3, 4); console.log(newArr); //[ 4, 2, 3, 4, 5 ]
-
描述
-
范围:[start, end)
-
target:从…开始替换
-
end > length - 1 取到末尾
const newArr = arr.copyWithin(0, 3, 10); console.log(newArr); //[ 4, 5, 3, 4, 5 ]
-
target > length -1 不发生任何替换
const newArr = arr.copyWithin(5, 3, 4); console.log(newArr); //[ 1, 2, 3, 4, 5 ]
-
当target > start 正常替换
const newArr = arr.copyWithin(3, 1, 3); console.log(newArr); //[ 1, 2, 3, 2, 3 ]
-
start或end是负数,则:[start+length, end+length)
const newArr = arr.copyWithin(0, -3, -1); //0 2 4 console.log(newArr); //[ 3, 4, 3, 4, 5 ]
-
如果没有start,取整个数组的元素
copyWithin 是不改变数组长度的,超出部分截掉
const newArr = arr.copyWithin(3); console.log(newArr); //[ 1, 2, 3, 1, 2 ]
-
如果没有end,则取到末尾
const newArr = arr.copyWithin(5, 3); console.log(newArr); //[ 1, 4, 5, 4, 5 ]
-
返回的是原数组引用
console.log(newArr == arr); // true
-
-
拷贝对象/数组
-
拷贝数组(元素值是引用的情况)
const arr = [{ id: 1, name: 'Tom1' },{ id: 2, name: 'Tom2' },{ id: 3, name: 'Tom3' },{ id: 4, name: 'Tom4' },{ id: 5, name: 'Tom5' }]; const target1 = arr[0]; const target3 = arr[2]; const newArr = arr.copyWithin(0, 2, 3); const target2 = arr[0]; console.log(target1 === target2); // false console.log(target3 === target1); // true // 直接把 arr[2] 的引用复制了一份到 arr[0],浅拷贝
-
拷贝对象
// this 不一定非要指向一个数组,也可以指向一个对象 var obj = { 0: 1, 1: 2, 2: 3, 3: 4, 4: 5, length: 5 } const newObj = [].copyWithin.call(obj, 0, 2, 4); console.log(newObj); // { '0': 3, '1': 4, '2': 3, '3': 4, '4': 5, length: 5 } console.log(obj === newObj) // true 是同一个引用
-
-
位移
-
有符号左/右位移(<</>>)
备注:二进制码最高位0是正数,1是负数。在JS里面常用 >> 0 保证字符是数字,>>> 0 用于保证数字是正整数。
正数高位补0,负数高位补1
1 >> 2; // 10 => 2 // 不知道是不是数字的情况,保证是一个数字,若是数字则保持不变,若不是则会变为0 undefined >> 0; // 0 'abc' >> 0; // 0
-
无符号右位移(>>>)
不管正负数,高位一律补0
xxx.length >>> 0; // 已知是数字的情况,保证数字是正整数
-
-
重写
Array.prototype.myCopyWithin = function(target){ // 判断没有this指向的情况 if(this == null){ throw new TypeError('this is null or not defined'); } // 将 this 用 Object 包装一下,让其变为引用值,因为 this有可能是 原始值 var obj = Object(this), len = obj.length >>> 0, // 保证 是一个正整数 start = arguments[1], end = arguments[2], count = 0, dir = 0; // 保证 target 是一个整数 target = target >> 0; // 判断 target 是正数还是负数 target = target < 0 ? Math.max(len + target, 0) : // 取最大 Math.min(target, len); // 取最小 // 先判断 start 是否存在,存在则保证其为整数 start = start ? start >> 0 : 0; start = start < 0 ? Math.max(len + start, 0) : Math.min(start, len); end = end ? end >> 0 : len; end = end < 0 ? Math.max(len + end, 0) : Math.min(end, len); count = Math.min(end - start, len - target) if(start < target && target < (start + count)) { dir = -1; start += count - 1; target += count - 1; } while(count > 0) { if(start in obj) { obj[target] = obj[start]; } else { delete obj[target]; } start += dir; target += dir; count--; } return obj; } const arr = [1, 2, 3, 4, 5]; const newArr = arr.myCopyWithin(0, 3, 4); console.log(newArr);
2. generator与iterator(ES6)
-
铺垫
-
七种遍历数组的方法
- forEach -> 普通的数组遍历方法 -> 是对 es3 for 循环的优化
- map -> 映射 -> 每一次遍历,返回一个数组元素 -> 返回一个新的数组
- filter -> 过滤 -> 每一次遍历,返回 boolean,来决定当前元素是否纳入新的数组中
- reduce -> 归纳 -> 每一次遍历,将当前元素收归到容器中
- reductRight -> reduce的反向操作
- every -> 判定是否所有元素都符合一个条件
- some -> 判定是否有某一个或多个符合一个条件
底层都是 for 循环实现
-
遍历与迭代
遍历 -> 一次性对数组中每一个元素进行查询和处理
迭代 -> 遍历的过程是可控的(遍历的过程可停止,也可继续),手动的控制遍历流程
产品迭代 -> 人为控制的产品升级与扩展 -> manual control
遍历是迭代一次又一次的实现,迭代是遍历的底层方法
-
-
概念
生成器是一个函数
迭代器是由生成器函数执行后返回的一个带有next方法的对象
生成器对迭代的控制是由 yield 关键字来执行的
-
写法
function * generator(){ // 每次 yield 就是产出一个迭代器对象, // 包含了value: 当前yield的值,done:当且生成器里需要迭代的值完成了没有 yield 'name: tom'; yield 'age: 18'; yield 'hobby: running'; return 'js' } const iterator = generator(); console.log(iterator.next()); console.log(iterator.next()); console.log(iterator.next()); console.log(iterator.next());
const arr = ['name: tom', 'age: 18', 'hobby: running']; // 疑问:迭代器是怎么知道迭代完的呢? 具体请看迭代器实现 function * gen(arr){ // 对可迭代对象进行遍历循环 for(var i = 0; i < arr.length; i++){ // 通过 yield 产出一个值,并且停止下来 yield arr[i]; } return 'js' } const iterator = gen(); // 每次迭代都是手动控制 // 每次遍历都是迭代的过程 console.log(iterator.next()); console.log(iterator.next()); console.log(iterator.next()); console.log(iterator.next());
-
迭代器实现
const arr = ['name: tom', 'age: 18', 'hobby: running']; // 利用闭包的原理实现生成器函数 function gen(arr) { var nextIndex = 0; return { next: function(){ return nextIndex < arr.length ? {value: arr[nextIndex++], done: false} : {value: arr[nextIndex++], done: true} } } } const iterator = gen(); console.log(iterator.next()); console.log(iterator.next()); console.log(iterator.next()); console.log(iterator.next());
3. Array.prototype.entries(ES6)
-
概念
返回一个新的Array Iterator对象,该对象包含数组中每个索引的键/值对
-
写法
arr.entries()
返回值:一个新的 Array 迭代器对象。Array Iterator是对象,它的原型(proto:Array Iterator)上有一个next方法,可用用于遍历迭代器取得原数组的[key,value]。
const arr = ['a', 'b', 'c']; const it = arr.entries(); console.log(it); //Object [Array Iterator] -> 说明可以调用 next console.log(it.next()); // { value: [ 0, 'a' ], done: false } for (let c of it) { const [i, v] = c; console.log(i, v); } // 0 a // 1 b // 2 c
-
迭代对象实现
-
普通遍历
var o = { a: 1, b: 2, c: 3 } // for of 是用来遍历可迭代对象的 // for of 运行原理,Symbol.iterator,调用iterator迭代器里的next进行一步步的遍历 // 并直接返回 {value:...,done:...} 的value给你 for(let v of arr){ console.log(v); // 报错,因为对象没有迭代器接口 }
-
部署迭代器接口
-
方法一:对象上部署
// 类数组(模拟) var o = { 0: 1, 1: 2, 2: 3, length: 3, // 通过[Symbol.iterator]判断是否是一个可迭代对象,并部署接口 [Symbol.iterator]: Array.prototype[Symbol.iterator] } for(let v of it){ console.log(v); // 1 2 3 }
-
方法二:原型上部署
// 写在原型上也是可以的 Object.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator]; for(let v of o) { console.log(v); // 1 2 3 }
-
方法三:转成数组
// 将 o 转成数组 for(let v of Array.from(o)) { console.log(v); // 1 2 3 }
-
-
-
next运行实现
一次迭代完,迭代完就停止
const arr = [1, 2, 3, 4, 5]; const it = arr.entries(); var newArr = []; // 保留 value 的具体信息 for(var i = 0; i < arr.length + 1; i++){ var item = it.next(); !item.done && (newArr[i] = item.value) } console.log(newArr); // [ [ 0, 1 ], [ 1, 2 ], [ 2, 3 ], [ 3, 4 ], [ 4, 5 ] ]
-
二维数组排序
const newArr = [ [56, 23], [56, 34, 100, 1], [123, 234, 12] ] function sortArr (arr){ var _it = arr.entries(), // 拿到键/值构成的数组 _doNext = true; // 是否要进行下一次迭代 while(_doNext){ var _r = _it.next(); // 对数组进行迭代 if(!_r.done){ // 判断迭代是否结束 _r.value[1].sort(function(a, b){ // 拿到迭代抽取的值进行排序 return a - b; }); _doNext = true; } else { _doNext = false; } } return arr; } console.log(sortArr(newArr)); // [ [ 23, 56 ], [ 1, 34, 56, 100 ], [ 12, 123, 234 ] ]
4. Array.prototype.fill(ES6)
-
概念
用于将一个值填充到数组中从起始索引到终止索引内的全部元素。不包括终止索引。
-
写法
arr.fill(value[, start[, end]])
value:用来填充数组元素的值。可选,默认全部填充undefined
start:起始索引,默认值为0。可选,默认0
end:终止索引,默认值为 this.length。可选,arr.length
返回值:原数组引用
const arr = [1, 2, 3, 4, 5]; // 填充 a 至 2 到 4 位 console.log(arr.fill('a', 2, 4)); // [1, 2, 'a', 'a', 5]
-
描述
-
范围:[start, end)
-
返回值是数组引用,修改原数组
const newArr = arr.fill('a', 2, 4); console.log(arr === newArr); // true
-
没有end则取至末尾
console.log(arr.fill('a', 1)); // [1, 'a', 'a', 'a', 'a']
-
没有start则全部替换
console.log(arr.fill('a')); // ['a', 'a', 'a', 'a', 'a']
-
start/end为负数的情况 [start+length, end+length)
console.log(arr.fill('e', -4, -2)); // e 1 3 // [ 1, 'e', 'e', 4, 5 ]
-
没有参数, 则全部覆盖为undefined
console.log(arr.fill()); // [ undefined, undefined, undefined, undefined, undefined ]
-
start === end 的情况,则不变
console.log(arr.fill('f', 1, 1)); // [1,1) 空集 // [ 1, 2, 3, 4, 5 ]
-
start/end是非数, 默认取0
console.log(arr.fill('g', 'a', 'b')); // [ 1, 2, 3, 4, 5 ] console.log(arr.fill('g', 1, 'b')); // [1,0) 空集 // [ 1, 2, 3, 4, 5 ] console.log(arr.fill('g', 'a', 4)); // [0,4) // [ 'g', 'g', 'g', 'g', 5 ]
-
start/end是NaN, 默认取0
console.log(arr.fill('h', NaN, NaN)); // [ 1, 2, 3, 4, 5 ] console.log(arr.fill('h', 1, NaN)); // [1,0) 空集 // [ 1, 2, 3, 4, 5 ] console.log(arr.fill('h', NaN, 4)); // [0,4) // [ 'h', 'h', 'h', 'h', 5 ]
-
start/end是null, 默认取0
console.log(arr.fill('i', null, null)); // [ 1, 2, 3, 4, 5 ] console.log(arr.fill('i', 1, null)); // [1,0) 空集 // [ 1, 2, 3, 4, 5 ] console.log(arr.fill('i', null, 4)); // [0,4) // [ 'i', 'i', 'i', 'i', 5 ]
-
start/end是undefined, 则看作没有填写
console.log(arr.fill('j', undefined, undefined)); // (0,length] // [ 'j', 'j', 'j', 'j', 'j' ] console.log(arr.fill('j', 1, undefined)); // (1,length] // [ 1, 'j', 'j', 'j', 'j' ] console.log(arr.fill('j', undefined, 4)); // (0, 4) 这里undefined看作0 // [ 'j', 'j', 'j', 'j', 5 ]
-
对象调用的情况
写了{length:3},fill会根据你的length去填充相应的数量的元素,值为你填入的value
const newObj = Array.prototype.fill.call({ length: 3 }, 4); console.log(newObj); // { '0': 4, '1': 4, '2': 4, length: 3 }
发现:此方法可以用来创建一个类数组
-
-
创建类数组方法
数组转类数组
function makeArrayLike(arr) { var arrLike = { length: arr.length, push: [].push, splice: [].splice } arr.forEach(function(item, index) { [].fill.call(arrLike, item, index, index + 1); }) return arrLike; } var newObj = makeArrayLike(['a', 'b', 'c', 'd']) console.log(newObj); // { // '0': 'a', // '1': 'b', // '2': 'c', // '3': 'd', // length: 4, // push: [Function: push], // splice: [Function: splice] // } // 也可以是引用值 var obj2 = makeArrayLike([{ id: 1, name: 'Tom1' }, { id: 2, name: 'Tom2' }, { id: 3, name: 'Tom3' }, { id: 4, name: 'Tom4' }, { id: 5, name: 'Tom5' }]); console.log(obj2); // { // '0': { id: 1, name: 'Tom1' }, // '1': { id: 2, name: 'Tom2' }, // '2': { id: 3, name: 'Tom3' }, // '3': { id: 4, name: 'Tom4' }, // '4': { id: 5, name: 'Tom5' }, // length: 5, // push: [Function: push], // splice: [Function: splice] // }
-
重写
Array.prototype.myFill = function(){ var value = arguments[0] || undefined, start = arguments[1] >> 0, // 不符合条件的都设为0,保证为数字 end = arguments[2]; // 判断没有this指向的情况 if(this == null){ throw new TypeError('this is null or not defined'); } var obj = Object(this), // 先包装成对象,如果不是对象则没有意义 len = obj.length >>> 0; start = start < 0 ? Math.max(len + start, 0) : Math.min(start, len); end = end === undefined ? len: end >> 0; end = end < 0 ? Math.max(len + end, 0) : Math.min(end, len); while(start < end){ obj[start] = value; start++; } return obj; } var arr = [1, 2, 3, 4, 5]; const newArr = arr.myFill('a', 2, 4); console.log(newArr); // [ 1, 2, 'a', 'a', 5 ]
5. Array.prototype.find(ES6)
-
概念
返回数组中满足提供的测试函数的第一个元素的值。否则返回 undefined。
-
写法
arr.find(callback[, thisArg])
callback:在数组每一项上执行的函数,接收 element/index/array 3个参数
element:当前遍历到的元素。
index:当前遍历到的索引。
array:数组本身。
thisArg:执行回调时用作 this 的对象。
返回值:数组中第一个满足所提供测试函数的元素的值,否则返回 undefined。
const arr = [1, 2, 3, 4, 5]; // 返回第一个满足条件的数组元素 const item = arr.find(item => item > 3); console.log(item); // 4
-
描述
-
返回第一个满足条件的数组元素
const item = arr.find(item => item > 3); console.log(item); // 4
-
如果没有一个元素满足条件,返回 undefined
const item2 = arr.find(function(item) { return item > 5 }) console.log(item2); // undefined
-
数组元素是引用值的情况
const arr = [{ id: 1, name: '张三' }, { id: 2, name: '李四' }, { id: 3, name: '王五' }]; const item = arr.find(item => item.name === '李四'); console.log(item); // { id: 2, name: '李四' } // 返回的元素和数组对应下标的元素是同一个引用 console.log(item === arr[1]); // true
-
参数问题
回调函数:当前遍历的元素、当前遍历的元素对应的下标、当前的数组
回调返回值:boolean,遍历在某一次调用回调后返回true,停止
第二个参数:
更改回调函数内部的 this 指向
非严格模式环境下,this->window
严格模式下,不传入第二个参数,this为undefined,与严格模式规定相统一
const item2 = arr.find(function(item, index, arr){ console.log(item, index, arr); // { id: 1, name: '张三' } 0 [ { id: 1, name: '张三' }, { id: 2, name: '李四' }, { id: 3, name: '王五' } ] // ... console.log(this); // {a: 1} }, {a: 1})
-
回调函数的返回值是 布尔类型,第一个返回 true 的对应数组元素作为 find 的返回值
const item = arr.find(function(item){ return item.id > 1; // boolean }) console.log(item);
-
稀松数组
稀松数组 -> 数组元素与元素之间是有空隙的
const arr = Array(5); console.log(arr); // empty * 5 arr[0] = 1; arr[2] = 3; arr[4] = 5; // [1, , 3, , 5] 稀疏数组 -> 数组元素与元素之间是有空隙的 // find 会遍历稀疏数组的空隙 -> empty,具体遍历出的值,由 undefined 占位 const item = arr.find(function(item){ console.log(item); // 1 undefind 3 undefined 5 return false; }) // ES5 数组扩展方法只会遍历有值的索引,下面的几种遍历方法也是一样 arr.forEach(function(item){ console.log(item); // 1 3 5 }) arr.map(function(item){ console.log(item); // 1 3 5 }) arr.filter(function(item){ console.log(item); // 1 3 5 }) arr.reduce(function(item){ console.log('Gone'); // 'Gone' 'Gone' 'Gone' },[]) arr.reduceRight(function(item){ console.log('Gone'); // 'Gone' 'Gone' 'Gone' },[]) arr.every(function(item){ console.log(item); // 1 3 5 return true; }) arr.some(function(item){ console.log(item); // 1 3 5 return false; }); // 结论:find的遍历效率是低于ES5扩展方法的
-
遍历时对元素进行操作
find 是不会更改数组的
新增了元素,但是 find 会在第一次执行回调函数的时候,拿到这个函数最初的索引范围
const arr = [1,2,3,4,5]; const item = arr.find(function(item){ console.log('Gone'); item = item+1; }) // find 是不会更改数组的 console.log(arr); // [1,2,3,4,5] // 新增了元素,但是 find 会在第一次执行回调函数的时候,拿到这个函数最初的索引范围,然后追加上去 const item1 = arr.find(function(item){ arr.push(6); console.log(item); // 1 2 3 4 5 }) console.log(arr); // [1, 2, 3, 4, 5, 6, 6, 6, 6, 6]
splice删除,删除了对应项,当前位置不保留,在数据最后补上undefined
delete/pop删除,删除该项的值,保留当前位置,并填入 undefined
const arr = [1,2,3,,,,7,8,9]; arr.find(function(item, index){ if(index === 0){ arr.splice(1, 1); // splice删除,删除了对应项,该项位置不保留,在数据最后补上undefined } console.log(item); // 1 3 undefined undefined undefined 7 8 9 undefined }) console.log(arr); // [ 1, 3, <3 empty items>, 7, 8, 9 ] arr.find(function(item, index){ if(index === 0){ delete arr[2]; // 删除该项的值,并填入 undefined } console.log(item); // 1 3 4 5 undefined undefined undefined undefined 7 8 9 }) console.log(arr); // [ 1, 2, <4 empty items>, 7, 8, 9 ] arr.find(function(item, index){ if(index === 0){ arr.pop(); // 删除该项的值,并填入 undefined } console.log(item); // 1 2 3 undefined undefined undefined 7 8 undefined }) console.log(arr); // [ 1, 2, 3, <3 empty items>, 7, 8 ]
-
-
重写
Array.prototype.myFind = function(cb){ if(this === null){ throw new TypeError('this is null'); } if(typeof cb !== 'function'){ throw new TypeError('Callback must be a function type'); } // 把 this 包装成一个对象 var obj = Object(this), len = obj.length >>> 0, // 无符号位移,保证是一个正整数 arg2 = arguments[1], // 有可能传入了第二个参数 step = 0; // 下标 while(step < len){ var value = obj[step]; // 找到数组下标对应的这一项 if(cb.apply(arg2, [value, step, obj])){ // 如果返回的是真,返回value // 第一个返回 true 的对应数组元素作为 find 的返回值 return value; } step++; } return undefined; // 没有找到,返回 undefined } const arr = [1, 2, 3, 4, 5]; const item = arr.myFind(item => item > 3); console.log(item);
6. Array.prototype.findIndex(ES6)
-
概念
返回数组中满足提供的测试函数的第一个元素的索引。若没有找到对应元素则返回-1。
-
写法
arr.findIndex(callback[, thisArg])
callback:针对数组中的每个元素, 都会执行该回调函数, 执行时会自动传入element/index/array三个参数
element:当前元素。
index:当前元素的索引。
array:调用findIndex的数组。
thisArg:可选。执行callback时作为this对象的值.
返回值: 数组中通过提供测试函数的第一个元素的索引。否则,返回-1
const arr = [1, 2, 3, 4, 5]; const idx = arr.findIndex(item => item > 2); console.log(idx); // 2
-
描述
-
返回第一个满足条件的数组对应的元素下标 2 => 3
const idx = arr.findIndex(item => item > 2); const item = arr.find(item => item > 2); console.log(idx, item); // 2 3
-
如果没有找到符合条件的元素,则返回-1
const idx = arr.findIndex(item => item > 5); console.log(idx); // -1
-
数组长度为空的情况,返回-1
const arr1 = []; const idx = arr1.findIndex(item => item > 2); console.log(idx); // -1
-
稀疏数组是正常遍历空隙,空隙将会被填充为undefined
const arr1 = [,2,,,,,,]; const idx = arr1.findIndex(function(item){ console.log(item); // undefined 2 return item === 2; });
-
findIndex如果回调返回了true,遍历就停止
-
findIndex会遍历空隙,而ES5拓展方法只会遍历有值的索引项
const idx = arr1.findIndex(function(item){ console.log('Gone'); // Gone * 7 }); // 有值的索引项 arr1.some(function(item){ console.log('Gone'); // Gone * 1 return false; }); arr1.every(function(item){ console.log('Gone'); // Gone * 1 return true; }) arr1.forEach(function(item){ console.log('Gone'); // Gone * 1 }) arr1.map(function(item){ console.log('Gone'); // Gone * 1 }) arr1.filter(function(item){ console.log('Gone'); // Gone * 1 }) arr1.reduce(function(item){ console.log('Gone'); // Gone * 1 },[]);
-
参数问题
回调函数:遍历的当前数组元素、元素对应的下标、源数组
回调返回值:boolean,遍历在某一次调用回调后返回true,停止
第二个参数:
更改回调内部的this指向
默认情况下 this -> window
设置了第二个参数:this -> arg2
严格模式下:this -> undefined
-
回调函数内部是无法改变数组的元素值
const idx = arr.findIndex(function(item){ item += 1; }); console.log(arr); // [1,2,3,4,5]
-
虽然增加了元素,但是遍历只会进行5次
findIndex在第一次调用回调函数的时候确认数组的范围 5
const idx = arr.findIndex(function(item,index){ console.log('Gone'); // Gone * 5 console.log(item); // 1 2 3 4 5 }); console.log(arr); // [1,2,3,4,5,6,6,6,6,6]
-
删除时要注意的
const idx = arr.findIndex(function(item, index){ if (index === 0) { // 最后走的一次(第五次)补undefined,实际上数组被删除了第1项 arr.splice(1,1); // arr -> [1,3,4,5,undefined] // 删除对应下标的值并补undefined,实际数组中,对应下标变成空隙 empty delete arr[1]; // arr -> [1,undefined,3,4,5] // 删除元素下标对应的值,补undefined,实际数组被删除了最后1项 arr.pop(); // arr -> [1,2,3,4,undefined] } })
-
-
重写
// 'use strict'; // 在严格模式下 函数 this 默认指向 null 或 undefined, call(null/undefined)->null/undefined // 非严格模式下,默认指向window,call(null/undefined)->window Array.prototype.myFindIndex = function(cb) { if (this == null) { throw new TypeError('this is null'); } if (typeof cb != 'function') { throw new TypeError('Callback must be a function'); } var obj = Object(this), len = obj.length >>> 0, arg2 = arguments[1], // 设置this指向,后面不需要写window,因为非严格模式不传第二个参数默认就是window,严格模式下是undefined,如果写了那么在严格模式下也window,不符合预期 step = 0; while (step < len) { var value = obj[step]; if (cb.apply(arg2, [value, step, obj])) { // 通过apply将参数传给回调函数,如果回调返回为true,停止循环并返回元素索引 return step; } step++; } return -1; } const arr = [1, 2, 3, 4, 5]; const idx = arr.myFindIndex(function(item, index, arr) { console.log(item, index, arr); // 1 0 [ 1, 2, 3, 4, 5 ] ... console.log(this); // { a: 1 } return item > 2; }, { a: 1 }) console.log(idx); // 2
7. Array.prototype.flat(ES7)
-
概念
flat:扁平的
返回一个新的数组,多维数组 -> 一维数组
-
写法
var newArray = arr.flat([depth])
depth:指定要提取嵌套数组的结构深度,默认值为 1。
返回值:一个包含将数组与子数组中所有元素的新数组。
const arr = [0, 1, [2, 3], 4, 5]; console.log(arr.flat()); // [0, 1, 2, 3, 4, 5] const arr2 = [0, 1, 2, [[[3, 4]]]]; console.log(arr2.flat(2)); // expected output: [0, 1, 2, [3, 4]]
-
描述
-
返回一个新的数组,说明不修改原数组
const arr = [0, 1, [2, 3], 4, 5]; const newArr = arr.flat(); console.log(arr === newArr); // false
-
参数
-
默认参数
flat默认情况下参数是1 -> 向内深入一层
参数:结构深度 默认为1,向数组内部深入一层,两层
flat 默认情况下是浅扁平化
const arr = [0, 1, [2, 3], 4, 5]; // 参数为1 console.log(arr.flat()); // [0, 1, 2, 3, 4, 5] console.log(arr.flat(1)); // 结果一致 // 参数为2 const arr1 = [0, 1, [2, [3, 4], 5], 6]; console.log(arr.flat()); // [ 0, 1, 2, [ 3, 4 ], 5, 6 ] console.log(arr.flat(2)); // [1, 2, 3, 4, 5, 6]
-
Infinity
正无穷 Infinity 结构深度 正无穷大的设置
const arr = [0,[1,2,[3,4,[5,6,[7,8,[9]]]]]] const newArr = arr.flat(infinity); console.log(newArr); // [1,2,3,4,5,6,7,8,9]
-
特殊参数
0/负数/非数字符串 -> 不做任何扁平化处理
数字字符串/布尔/ -> Number -> 数字类型
数字必须从1开始填写 -> Infinity
const arr = [0,1,[2,[3,4],5,]6]; console.log(arr.flat(-2)); // [0,1,[2,[3,4],5,]6] console.log(arr.flat('a')); // [0,1,[2,[3,4],5,]6] console.log(arr.flat(0)); // [0,1,[2,[3,4],5,]6] console.log(arr.flat(false)); // [0,1,[2,[3,4],5,]6] console.log(arr.flat(true)); // [1,2,3,4,5,6] console.log(arr.flat(3)); // [1,2,3,4,5,6]
-
-
稀疏数组
剔除所有的数组空隙 empty
const arr = [1, , [2, , [3, 4, , 5, , , 6, [7, , 8, 9, , [0]]]]]; // 剔除所有的数组空隙empty const newArr = arr.flat(Infinity); console.log(newArr); //
-
concat
可以放入多个数组元素或者其他数组
var a = 1, b = [2, 3], c = [3, 4]; const newArr = b.concat(a, c); console.log(newArr); // [ 2, 3, 1, 3, 4 ]
-
-
浅扁平化实现
const arr = [0, [1, 2, [3, 4, [5, 6, [7, 8, [9]]]]]]; // 方法1:利用 reduce function shallowFlat(arr) { return arr.reduce(function(prev, current) { return prev.concat(current); }, []) } console.log(shallowFlat(arr)) // 方法2 function shallowFlat(arr) { return [].concat(...arr); } console.log(shallowFlat(arr)); // [1,2,3,4,5,6,7,8,9]
-
深度扁平化实现
-
方法1:reduce + concat + isArray + 递归
const arr = [0, [1, 2, [3, 4, [5, 6, [7, 8, [9]]]]]]; Array.prototype.deepFlat = function() { var arr = this, deep = arguments[0] !== Infinity ? arguments[0] >>> 0 : Infinity; return deep > 0 ? arr.reduce(function(prev, current) { return prev.concat(Array.isArray(current) ? current.deepFlat(deep - 1) : current) }, []) : arr; } console.log(arr.deepFlat(Infinity)); // [1,2,3,4,5,6,7,8,9]
-
方法2:forEach + isArray + push + 递归
const arr = [0, [1, 2, [3, 4, [5, 6, [7, 8, [9]]]]]]; Array.prototype.deepFlat = function() { var arr = this, deep = arguments[0] !== Infinity ? arguments[0] >>> 0 : Infinity, res = []; (function _(arr, deep) { // 在独立的作用域里操作 // 数组扩展方法时会剔除 empty arr.forEach(function(item) { if (Array.isArray(item) && deep > 0) { _(item, deep - 1) } else { res.push(item) } }) })(arr, deep) return res; } console.log(arr.deepFlat(Infinity)); // [1,2,3,4,5,6,7,8,9]
-
方法3:方法3:for of + isArray + push 去掉empty
const arr = [0, [1, 2, [3, 4, [5, 6, [7, 8, [9]]]]]]; Array.prototype.deepFlat = function() { var arr = this, deep = arguments[0] !== Infinity ? arguments[0] >>> 0 : Infinity, res = []; (function _(arr, deep) { // 在独立的作用域里操作 // 数组扩展方法时会剔除 empty for (var item of arr) { if (Array.isArray(item) && deep > 0) { _(item, deep - 1) } else { item !== void 0 && res.push(item); // for...of 判断empty 要用void 0 } } })(arr, deep) return res; } console.log(arr.deepFlat(Infinity)); // [1,2,3,4,5,6,7,8,9]
-
方法4:stack栈 pop + push(重要)
const arr = [0, [1, 2, [3, 4, [5, 6, [7, 8, [9]]]]]]; Array.prototype.deepFlat = function() { var arr = this, stack = [...arr], res = []; while (stack.length) { const popItem = stack.pop(); if (Array.isArray(popItem)) { stack.push(...popItem); } else { res.push(popItem); } } return res.reverse(); } console.log(arr.deepFlat(Infinity)); // [1,2,3,4,5,6,7,8,9]
-
方法5:纯递归实现
const arr = [0, [1, 2, [3, 4, [5, 6, [7, 8, [9]]]]]]; Array.prototype.deepFlat = function() { var arr = this, res = []; (function _(arr) { arr.forEach(function(item) { if (Array.isArray(item)) { _(item) } else { res.push(item) } }) })(arr) return res; } console.log(arr.deepFlat(Infinity)); // [1,2,3,4,5,6,7,8,9]
-
方法6:生成器函数
const arr = [0, [1, 2, [3, 4, [5, 6, [7, 8, [9]]]]]]; function* deepFlat(arr) { for (var item of arr) { if (Array.isArray(item)) { yield* deepFlat(item); } else { yield item; } } } // 使用的时候注意,rest运算符可以直接拿到迭代器迭代的结果 console.log([...deepFlat(arr)]); // [1,2,3,4,5,6,7,8,9]
-
8. Array.prototype.flatMap(ES7)
-
概念
兼容性不太好
flat + map -> map遍历后返回进行浅扁平化(注意:flat深度是1)
-
写法
var new_array = arr.flatMap(function callback(currentValue[, index[, array]]) { // return element for new_array }[, thisArg]);
callback:可以生成一个新数组中的元素的函数,可以传入currentValue/index/array个参数
currentValue:当前正在数组中处理的元素
index:可选的。数组中正在处理的当前元素的索引。
array:可选的。被调用的 map 数组
thisArg:可选的。执行 callback 函数时 使用的this 值。
返回值: 一个新的数组,其中每个元素都是回调函数的结果,并且结构深度 depth 值为1。
const arr = ['123', '456', '789']; const newArr = arr.flatMap(function(item) { return item.split(''); }); console.log(newArr); // [1,2,3,4,5,6,7,8,9]
-
描述
-
flat + map
const arr = ['123', '456', '789']; // 通过split把每一个字符串分开变成该数组的元素,最后出来的结果是一个二维数组 const newArr = arr.map(function(item) { return item.split(''); }); console.log(newArr); // [ [ '1', '2', '3' ], [ '4', '5', '6' ], [ '7', '8', '9' ] ] // flat + map = flatMap -> 把二维数组变一维数组 const newArr = arr.map(function(item) { return item.split(''); }).flat(); // 后面加个flat就可以了 console.log(newArr); // [1,2,3,4,5,6,7,8,9] // flatMap: // 1. 遍历 + 扁平化 // 2. map + flat // 3. 效率高一些,一体化完成 // 4. 返回值是一个新的数组 const newArr = arr.flatMap(function(item) { return item.split(''); // return时帮你进行扁平化的操作 }); console.log(newArr); // [1,2,3,4,5,6,7,8,9]
-
返回值
返回值是一个新的数组
console.log(newArr === arr); // false
-
参数
回调参数:当前遍历的元素、当前遍历的元素在数组中对应的下标、 数组本身
回调函数中 this 默认指向 window
严格模式下,this为undefined(这里说指向是错误的)
flatMap的第二个参数可以更改回调内的this指向
const arr = ['123', '456', '789']; const newArr = arr.flatMap(function(item, index, arr) { console.log(item, index, arr); // 123 0 ['123', '456', '789'] ... console.log(this); // {a:1} }, { a: 1 });
-
-
使用场景
-
处理字符串
对有些字符串可能会需要做统计的处理,就可以用到flatMap
const arr = ['Hello world', 'Today is sunday']; const newArr1 = arr.map(function(item) { return item.split(' ') }) const newArr = arr.flatMap(function(item) { return item.split(' ') }) console.log(newArr); // [ 'Hello', 'world', 'Today', 'is', 'sunday' ]
-
做运算,并增加项(遇到负数就和前一个数相加,并放入数组)
我们可以自主的形成数组,把我们想要添加的项放进这个数组,就可以用到flatMap
const arr = [1, -2, -3, 5, 8, -9, 6, 7, 0]; const newArr = arr.flatMap(function(item, index) { if (item < 0 && index >= 1) { return [item, `${item} + ${arr[index-1]} = ${item + arr[index-1]}`] } return item; }) console.log(newArr); // [ 1, -2, '-2 + 1 = -1', -3, '-3 + -2 = -5', 5, 8, -9, '-9 + 8 = -1', 6, 7, 0 ]
-
-
重写
TODO:这只是浅层的重写,还有一些情况并没有考虑到,比如深拷贝
const arr = ['123', '456', '789']; Array.prototype.myFlatMap = function(cb) { if (typeof cb !== 'function') { throw new TypeError('Callback must be a function'); } var arr = this, arg2 = arguments[1], res = [], item; for (var i = 0; i < arr.length; i++) { item = cb.apply(arg2, [arr[i], i, arr]); // 这里arr[i]要深度克隆一下 item && res.push(item) } return res.flat(); // 这里可以用自己写的deepFlat替换 } const newArr = arr.flatMap(function(item) { return item.split(''); }) console.log(newArr === arr);
9. Array.from(ES6)
-
概念
对一个类似数组或可迭代对象(Map/Set )创建一个新的,浅拷贝的数组实例。
-
写法
Array.from(arrayLike[, mapFn[, thisArg]])
arrayLike:想要转换成数组的伪数组对象或可迭代对象。
mapFn:如果指定了该参数,新数组中的每个元素会执行该回调函数。
thisArg:可选参数,执行回调函数 mapFn 时 this 对象。
返回值:一个新的数组实例。
console.log(Array.from('foo')); // ["f", "o", "o"]
-
描述
-
返回值
返回一个新的数组引用
const arr = [1, 2, 3]; // 返回一个新的数组引用 const newArr = Array.from(arr); console.log(newArr); // [1,2,3] console.log(arr === newArr); // false
-
参数
-
说明
Array.from 的第一个参数必须要是可迭代对象或者是标准的类数组
可迭代对象判断的标准:原型上有Symbol.
标准的类数组:不一定是真正的类数组,但键名要为索引和length长度
-
第一个参数
-
参数是一个数组
如果参数是一个带有引用类型元素的数组,返回的新数组是一个浅拷贝
const arr = [{ id: 1, name: '张三' }, { id: 2, name: '李四' }, { id: 3, name: '王五' }] // 返回的新数组是一个浅拷贝(拷贝的是引用) const newArr = Array.from(arr); console.log(arr[1] === newArr[1]); // true console.log(newArr); // [{...}, {...}, {...}]
-
参数是一个字符串
一个字符串,从底层来说就是用String构造函数构造出来的,所以可以理解为它是一个可迭代对象
const str = '123'; const newArr = Array.from(str); console.log(newArr); // ['1','2','3']
-
参数是一个Symbol类型
Array.from不做处理,并且返回一个空数组
因为Symbol是一个唯一的值,如果放入数组了就不是唯一了
const sm = Symbol('123'); // Symbol是一个唯一的值 const newArr = Array.from(sm); console.log(newArr) // []
-
参数是一个数字
Number不可迭代,Array.from不做处理,并且返回一个空数组
const num = 123; const newArr = Array.from(num); console.log(newArr) // []
-
参数是一个boolean
Boolean不可迭代,Array.from不做处理,并且返回一个空数组
const bool = true; const newArr = Array.from(bool); console.log(newArr) // []
-
参数是一个正则
Reg不可迭代,Array.from不做处理,并且返回一个空数组
const reg = /123/; const newArr = Array.from(reg); console.log(newArr) // []
-
参数是一个null/undefined
报错:null/undefined is not iterable
const newArr = Array.from(undefined); const newArr = Array.from(null); console.log(newArr); // 报错
-
参数为空
相当于里面有一个 undefined, 报错:undefined is not iterable
const newArr = Array.from(); console.log(newArr); // 报错
-
参数是一个普通对象
普通对象不可迭代,Array.from不做处理,并且返回一个空数组
const obj = { a: 1, b: 2, c: 3 } const newArr = Array.from(obj); console.log(newArr); // []
-
参数是一个类数组
正常返回一个对应的数组的必要条件:
-
键名必须从0开始按数字顺序排列
-
length 属性必须正确
长度决定了新数组的长度,属性名决定了填充该数组的位置
const arrLike = { 0: 1, 1: 2, 2: 3, length: 3 } const newArr = Array.from(arrLike); console.log(newArr); // [1,2,3] const arrLike = { a: 1, b: 2, c: 3, length: 3 } const newArr = Array.from(arrLike); console.log(newArr); // [undefined,undefined,undefined] const arrLike = { 1: 1, 2: 2, 3: 3, length: 3 } const newArr = Array.from(arrLike); console.log(newArr); // [undefined,1,2] const arrLike = { a: 1, b: 2, c: 3, length: 5 } const newArr = Array.from(arrLike); console.log(newArr); // [1,2,3,undefined,undefined]
-
-
参数是一个 Map
Map是一个可迭代对象,返回一个二维数组
const m = new Map([ ['a', 1], ['b', 2], ['c', 3], ]) console.log(m); // Map(3) { 'a' => 1, 'b' => 2, 'c' => 3 } const newArr = Array.from(m); console.log(newArr); // [ [ 'a', 1 ], [ 'b', 2 ], [ 'c', 3 ] ]
-
参数是一个 Set
Set是一个可迭代对象,可以正常返回数组
const s = new Set([1, 2, 3, 4]); console.log(s); // Set(4) { 1, 2, 3, 4 } const newArr = Array.from(s); console.log(newArr) // [1,2,3,4]
-
-
第二个参数
相当于map方法,在Array.from执行过程中进行调用,唯一不同的地方就是回调没有第三个参数。
由于回调执行的时候,Array.from还没有执行完毕,所以不存在逻辑上的新数组。
所以无法再回调里获取到新数组本身(有别于数组的其它遍历方法)
const arrLike = { 0: 1, 1: 2, 2: 3, length: 3 } const newArr = Array.from(arrLike, function(item, index, array) { console.log(item, index, array); // 1 0 undefined ... // 每一次遍历必须返回一个值 return item + 1; }); console.log(newArr); // [ 2, 3, 4 ] // from的第二个参数的执行原理 const newArr = Array.from(arrLike).map(function(item, index, array) { console.log(item, index, array); // 1 0 [1,2,3] return item + 1; }); console.log(newArr); // [ 2, 3, 4 ] // 二者区别: // 1. Array.from执行的过程中执行回调,故拿不到新数组 // 2. Array.from执行完后再调用map方法,故可以拿到新数组
-
第三个参数
第三个参数会更改回调内的this指向
const arrLike = { 0: 1, 1: 2, 2: 3, length: 3 } const newArr = Array.from(arrLike, function(item, index) { console.log(item); // 非严格模式下,回调内部的 this -> window // 严格模式下,回调内部的 this 为 undefined console.log(this); // {a:1} return item + 1; }, { a: 1 }); console.log(newArr); // [2, 3, 4]
-
-
形参长度
证明了from方法的第一个参数是必填项
console.log(Array.from.length) // 1
-
-
使用场景
-
填充数组 - 序列生成器
从1开始填充到10,每个数的间隔是2
const r = range(1, 10, 2); // (start, stop, step) // 实现 [1,3,5,7,9] console.log(r); function range(start, stop, step) { return Array.from({ length: (stop - start) / step + 1 // from 会直接把小数处理成整数 }, function(item, index) { return start + (index * step) }) }
-
数组的合并与去重
function combine() { const arr = Array.prototype.concat.apply([], arguments); return Array.from(new Set(arr)) // 用 set 去重 } const arr1 = [1, 2, 3, 4, 5]; const arr2 = [2, 3, 4, 5, 6]; const arr3 = [3, 4, 5, 6, 7]; console.log(combine(arr1, arr2, arr3)); // [1,2,3,4,5,6,7]
-
-
重写
三个参数:(可迭代对象或者类数组, mapFn, this指向)
可迭代对象或者类数组:必填
mapFn:可选
this指向:可选
// 用立即执行函数创建一个独立的作用域,存放函数和对参数的处理 Array.myFrom = (function() { // 判断是否可调用,保证 mapFn 是一个函数,如果不是,则报错 const isCallable = function(fn) { // 若 false 则不可调用 return typeof fn === 'function' || Object.prototype.toString.call(fn) === '[object Function]'; } // 把 传入的数值 转为整数 const toInt = function(value) { const v = Number(value); if (isNaN(v)) { return 0; // 非数 直接返回0 } // v 是 0 或者 不是一个无穷大的数 if (v === 0 || !isFinite(v)) { return v; } // 取绝对值,向下取整,乘上符号,变为正整数或负整数 return (v > 0 ? 1 : -1) * Math.floor(Math.abs(v)); } const maxSafeInt = Math.pow(2, 53) - 1; // js 的最大安全正整数 // 转换成 length const toLength = function(value) { const len = toInt(value); return Math.min( // 长度不能超多最大安全整数 Math.max(len, 0), // len 比 0 小,则取 0,len 比 0 大,证明它有长度,则取 len maxSafeInt ) } return function(arrLike) { const caller = this; // 谁调用了我 if (arrLike === null) { throw new TypeError('Method `from` requires an array-like object') } // 如果不是对象,包装成对象才能走下去 const origin = Object(arrLike); let arg2; // 改变 this 指向的值 // 判断是否有第二个参数,mapFn const mapFn = arguments.length > 1 ? arguments[1] : void undefined; if (typeof mapFn !== 'undefined') { if (!isCallable(mapFn)) { // 不可调用,抛错 throw new TypeError('mapFn must be a function') } if (arguments.length > 2) { arg2 = arguments[2]; } } const len = toLength(origin.length); // 确保调用myFrom返回的函数的是一个函数,因为可能出现(1).myFrom调用 const arr = isCallable(caller) ? Object(new caller(len)) : // 这里如果caller是Array,则相当于 new Array(3) new Array(len); let i = 0, val; while (i < len) { val = origin[i]; if (mapFn) { arr[i] = typeof arg2 === 'undefined' ? mapFn(val, i) : mapFn.apply(arg2, [val, i]); // 如果传了第三个参数,则更改this指向 } else { arr[i] = val; } i++; } return arr; } })(); const arrLike = { 0: 1, 1: 2, 2: 3, length: 3 } const newArr = Array.myFrom(arrLike, function(item, index) { console.log(item, index); return item + 1; }); console.log(newArr);
10. 相等性判断与Object.is方法(ES6)
-
铺垫
-
JS的相等性判断
截至 ES6 版本,有四种相等判断的算法:
- 全等(三等) ===
- 等于 ==
- 零值相等 -0 === +0
- 同值相等 -0 !== +0 NaN === NaN
JS 中提供有关相等判断的操作方法:
- 严格相等 === Strict Equality
- 非严格(抽象/非约束)相等 == Loose(自由的,不受限制的) Equality(标准的名词形容)
- Object.is(v1, v2) ES6 新的 API, 判断两个参数是否是同一个值
-
严格相等(===)
全等的优点:
- 全等对结果的预测是更加清晰明确
- 全等在不隐式类型转换的前提下,更快
// 1. 不进行隐式类型转换 —— 类型相同,值也相同 1 === '1'; // false 1 === 2; // false // 2. 引用值必须是同一地址 var obj = {}; obj === obj; // true {} === {}; // false 每次{},都是 new Object 出来一个新对象 // 3. 两个 NaN 或者是 NaN 跟其他值都不相等 NaN === NaN; // false; NaN === undefined; // false; // 4. +0 和 -0 相等 + 0 === -0; // true // 5. +Infinity 与 -Infinity 相等性 + Infinity === -Infinity; // false Infinity === Infinity; // true
如何定义变量 a,让 a !== a 为 true
var a = NaN; a !== a; // true
-
非严格相等(==)( Abstract equality(描述))
- 隐式类型转换 - 等式两边都有可能被转换,转换以后还是用严格相等来进行比较
- 任何对象和 null undefined 都不相等
与之互斥的是 窄对象 Narrow Object -> IE10 -> document.all -> 拿到 document 的所有节点 -> 其他浏览器不支持,后来只是待定为了 undefined -> typeof document.all === ‘undefined’document.all == undefined; // true typeof document.all === 'undefined' // true
注:直接写 {} == undefined 会报错,因为程序认为 {} 是个代码块,后面跟上 == 无法识别
({}) == undefined; // false 可以加上括号,将其变为表达式
ToPrimitive(A) 通过尝试调用 A 的 A.toString() 和 A.valueOf() 方法,将参数 A 转换为原始值(Primitive)
看 MDN 上的非严格相等表格 -> https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Equality_comparisons_and_sameness
-
同值相等 same-value
主要是针对0的
+0 不等于 -0 => 同值相等
NaN === NaN => 同值相等是正确的
同值相等的底层实现是 Object.is()
Object.is() 是 ES6 抛出来的方法,ES5并没有暴露 JS 引擎的同值相等的方法
var obj = {}; Object.defineProperty(obj, 'myZero', { value: -0, writable: false, configurable: false, enumerable: false }) // 再次定义,+0 或者 0 都会抛出异常,不能重新定义 myZero 属性,除非值相等 // value 为 -0 可以 Object.defineProperty(obj, 'myZero', { value: -0, }) // 证明了在Object.defineProperty定义value的时候,+0 和 -0 是不相等的值
NaN
var obj = {}; Object.defineProperty(obj, 'myNaN', { value: NaN, writable: false, configurable: false, enumerable: false }) Object.defineProperty(obj, 'myNaN', { value: NaN, writable: false, configurable: false, enumerable: false }) // 证明了在Object.defineProperty定义value的时候,NaN 和 NaN 是相等的
-
零值相等 same-value-zero
+0 和 -0 是相等的
-
-
概念
ES2015 ES6
同值相等的实现
Object.is() 的判断标准就是同值相等
-
写法
Object.is(value1, value2);
value1:被比较的第一个值。
value2:被比较的第二个值。
返回值:一个布尔值,表示两个参数是否是同一个值。
Object.is(undefined, undefined); // true; Object.is(null, null); // true; Object.is(true, true); // true; Object.is('1', '1'); // true; var obj = {}; Object.is(obj, obj); // true 同一个引用相等 Object.is({}, {}); // false 不同引用不相等 Object.is(1, 1); // true; Object.is(+0, +0); // true; Object.is(+0, -0); // false; Object.is(NaN, NaN); // true;
-
描述
-
不进行隐式类型转换
var a = 1; var b = '1'; Object.is(a, b)// false
-
+0不等于-0,NaN等于NaN -> 这也是和严格相等的区别
Object.is(+0, -0); // false Object.is(NaN, NaN); // true
-
-
重写
注:1/+0 = Infinity,1/-0 = -Infinity
Object.myIs = function(a, b) { if (a === b) { return a !== 0 || 1 / a === 1 / b; // 判断 +0 和 -0 } return a !== a && b !== b; // a!==a -> a = NaN, b!==b -> b=NaN } const res = Object.myIs(+0, -0); console.log(res) const res1 = Object.myIs(NaN, NaN); console.log(res1) const res2 = Object.myIs(1, 1); console.log(res2)
-
falsy的8个值
false +0 -0 8n ‘’ null undefined NaN
11. Array.prototype.includes(ES7)
-
概念
查询数组内是否包含某个元素
可以替换indexOf语法,解决indexOf的:1. 语义化不明显;2. NaN!==NaN的问题
-
写法
arr.includes(valueToFind[, fromIndex])
valueToFind:需要查找的元素值。
fromIndex:从fromIndex 索引处开始查找
返回值:返回一个布尔值 Boolean
-
描述
-
参数
-
第一个参数
要查找的数组元素,返回值是 bool
const arr = [1, 2, 3, 4, 5]; console.log(arr.includes(3)); // true console.log(arr.includes(6)); // false
-
第二个参数
fromIndex: 从下标几开始找,默认值为0
两个参数都不填,返回 false
const arr = [1, 2, 3, 4, 5]; console.log(arr.includes(5, 3)); // true console.log(arr.includes(3, 3)); // false console.log(arr.includes(3)); // true // 两个参数都不填,返回 false console.log(arr.includes()); // false // 形参长度是1,如果不填,默认是 undefined console.log(arr.includes.length); // 1 // 负数 arr.length + (-3) = 2 包含 fromIndex console.log(arr.includes(3, -3)); // true // fromIndex >= arr.length 直接 return false,不会对数组进行搜索 console.log(arr.includes(3, 5)); // false // arr.length + (-6) = -1 < 0 // 加完了之后还是负数,整个数组都会搜索,从0开始 console.log(arr.includes(3, -6)); // true
-
-
区分数字字符串与字母大小写
const arr = ['1', 'a', 3, 4, 5]; console.log(arr.includes(1)); // false console.log(arr.includes('A')); // false
-
String 使用 includes
var str = 'abcde'; console.log(str.includes('D')); // false console.log(str.includes('a')); // true
-
零值相等(same-value-zero)
var arr = [0, 1, 2, 3, 4, 5]; console.log(arr.includes(0)); // true console.log(arr.includes(-0)); // true console.log(arr.includes(+0)); // true
-
除了数组和字符串,其他类型的数据使用 includes
includes是一个通用方法,调用者不一定非要是数组和对象 -> 即this不一定是数组和对象
console.log(Array.prototype.includes.call(1, 'a')); // false console.log([].includes.call(true, 'a')); // false console.log([].includes.call({ // this -> 标准类数组 0: 'a', length: 1 }, 'a')); // true [NaN].includes(NaN); // true [NaN].indexOf(NaN); // -1
-
-
重写
Array.prototype.myIncludes = function(value) { if (this == null) { throw new TypeError('"this" is null'); } var fromIndex = arguments[1], obj = Object(this), // 变为引用值 len = obj.length >>> 0; if (len === 0) { return false; } // 位或运算 fromIndex = fromIndex | 0; // 如果 fromIndex 是 undefined, 那么就取 0,相当于下面注释的代码 // if (fromIndex === undefined) { // fromIndex = 0; // } fromIndex = Math.max( fromIndex >= 0 ? fromIndex : len + fromIndex, 0); while (fromIndex < len) { if (obj[fromIndex] === value) { return true; } fromIndex++; } return false; } const arr = [1, 2, 3, 4, 5]; console.log(arr.myIncludes(5, 3)); // true console.log(arr.myIncludes(3, 3)); // false console.log(arr.myIncludes(3)); // true console.log(arr.myIncludes()); // false console.log(arr.myIncludes.length); // 1 console.log(arr.myIncludes(3, -3)); // true console.log(arr.myIncludes(3, 5)); // false console.log(arr.myIncludes(3, -6)); // false
12. Array.prototype.sort(ES3)
-
概念
使用原地算法对数组的元素进行排序,并返回数组。
原地算法:
-
V8 -> arr.length <= 10 插入排序
arr.length > 10 快速排序
-
Mozilla 归并排序
-
Webkit 使用C++的QSort方法 快速排序的理念
-
-
写法
arr.sort([compareFunction])
compareFunction:比较函数方法(可选)-> 函数中两个参数a,b
自己写一个排序规则:
-
没有写排序规则,不进行任何排序操作
-
return 负数 -> a就排在b前面
return 正数 -> b就排在a前面
return 0 -> a与b不进行排列操作
var arr = [5, 1, 2, 4, 6, 3, 3]; console.log(arr.sort(function(a, b) { // 1. 没有写排序规则,不进行任何排序操作 })); console.log(arr.sort(function(a, b) { // a => 1 b => 5 console.log(a, b); if (a < b) { return -1; } if (a > b) { return 1; } if (a === b) { return 0; } })); // <b>纯数字</b>比较,可以简写为 console.log(arr.sort(function(a, b) { return a - b; // 从小到大排序 // return b - a; // 从大到小排序; // return -1; // a 都排在 b 前面 // return 1; // b 都排在 a 前面 // return 0; // 都不变 })); // 随机排序 console.log(arr.sort(function(a, b) { // compareFunction 必须对相同的输入有相同的返回结果,否则结果是不确定的 if (Math.random() > 0.5) { return 1; } if (Math.random() < 0.5) { return -1; } }));
返回值:返回排序后的原数组引用
var arr = [5, 3, 1, 2, 6, 4]; const newArr = arr.sort(); // 返回原数组的引用,不进行数组引用赋值 console.log(newArr === arr); // true
sort 并不会按照数字大小进行排序
toString -> 数组元素 -> 转为字符串
sort 默认按照 ASCII 码 升序排序
DOMString -> UTF-16字符串实现 -> 映射到String构造函数 -> 构建字符串
每一个string字符串 -> 都是 UTF-16字符串 -> 是 String/DOMString 的实例
按照UTF-16的编码顺序来进行排序 -> Unicode问:为什么要转成字符串呢?
答:如果是仅限一种类型的排序的话,sort功能性就太弱
用字符串和字符编码集合在一起形成排序规则,可排序的范围就大了var arr = [5, 3, 1000, 1, 6]; console.log(arr.sort()); var arr = ['b', 'a', 'e', 'd']; console.log(arr.sort()); var arr = [true, false]; console.log(arr.sort());
字符串排序 -> 字符串的逐个字符进行编码位的排序
var arr = ['abc', 'aba']; console.log(arr.sort());
-
-
描述
-
非ASCII字符串排序
var arr = ['你', '好', '啊']; console.log(arr.sort(function(a, b) { return a.localeCompare(b); // 非 ASCII 字符串排序 都用localeCompare⭐ }));
-
含大小写的字符串比较
var arr = ['JACK', 'Tom', 'annie', 'Crystal']; console.log(arr.sort(function(a, b) { var _a = a.toLowerCase(), // 每一次都会对数据进行处理,如果比较复杂的话,负载很高,可以先把数组的每一项简单化 _b = b.toLowerCase(); if (_a < _b) { return -1; } if (_a > _b) { return 1; } return 0; }));
优化
var arr = ['JACK', 'Tom', 'annie', 'Crystal']; // 通过 map 先把数组的每一项简单化,把数组的每一项处理好,再去 sort var newArr = arr.map(function(item, index) { var it = { index, value: item.toLowerCase() } return it; }); newArr.sort(function(a, b) { if (a.value < b.value) { return -1; } if (a.value > b.value) { return 1; } return 0; }) var res = newArr.map(function(item) { return arr[item.index] }) console.log(res);
-