【JS提升】JS内置对象常用方法

【JS提升】JS内置对象常用方法,第1张

目录 1. JavaScript内置对象方法:1.1. Array.prototype.copyWithin(ES2015 ES6)概念写法描述拷贝对象/数组位移重写 1.2. generator与iterator(ES2015 ES6)铺垫概念写法迭代器实现 1.3. Array.prototype.entries(ES2015 ES6)概念写法迭代对象实现next运行实现二维数组排序 1.4. Array.prototype.fill(ES2015 ES6)概念写法描述创建类数组方法重写 1.5. Array.prototype.find(ES2015 ES6)概念写法描述重写 1.6. Array.prototype.findIndex(ES2015 ES6)概念写法描述重写 1.7. Array.prototype.flat(ES2019)概念写法描述concat浅扁平化实现深度扁平化实现 1.8. Array.prototype.flatMap(ES2020 兼容性不太好)概念写法描述使用场景重写 1.9. Array.from(ES2015 ES6)概念写法描述使用场景重写 1.10. 相等性判断与Object.is方法(ES2015 ES6)铺垫概念写法描述重写falsy的8个值 1.11. Array.prototype.includes(ES2016 ES7)概念写法描述重写 1.12. Array.prototype.sort(ES3)概念写法描述

1. JavaScript内置对象方法: 1.1. Array.prototype.copyWithin(ES2015 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);
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 === 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

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);

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/1296368.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-06-10
下一篇 2022-06-10

发表评论

登录后才能评论

评论列表(0条)

保存