复制数组的一部分到同一数组中的另一个位置,返回原数组,不会改变原数组的长度
写法原理:复制元素集合 => 全选模板元素 => 粘贴
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);
1.2. generator与iterator(ES2015 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());
1.3. Array.prototype.entries(ES2015 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 ] ]
1.4. Array.prototype.fill(ES2015 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 ]
1.5. Array.prototype.find(ES2015 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);
1.6. Array.prototype.findIndex(ES2015 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
1.7. Array.prototype.flat(ES2019)
概念
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]
1.8. Array.prototype.flatMap(ES2020 兼容性不太好)
概念
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);
1.9. Array.from(ES2015 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);
1.10. 相等性判断与Object.is方法(ES2015 ES6)
铺垫
JS的相等性判断
截至 ES6 版本,有四种相等判断的算法:
全等(三等) ===等于 ==零值相等 -0 === +0同值相等 -0 !== +0 NaN === NaNJS 中提供有关相等判断的 *** 作方法:
严格相等 === 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
1.11. Array.prototype.includes(ES2016 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
1.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;
}
}));
// 纯数字比较,可以简写为
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);
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)