这段时间听同事面试校招生,发现自己原生js很弱,需要狂补,以下为看《一个合格的中级前端工程师要掌握的javaScript技巧》有感

背景

这段时间听同事面试校招生,发现自己原生js很弱,需要狂补,以下为看《一个合格的中级前端工程师要掌握的javaScript技巧》有感

1、判断对象的数据类型

1
2
3
const isType = type => target => `[Object ${type}]` === Object.prototype.toString.call(target);
const isArray = isType('Array');
console.log(isArray([])); // true

以上isType为高阶函数,可翻译如下:

1
2
3
4
5
6

const isType = (type) => {
(target) => {
return `[Object ${type}]` === Object.prototype.toString.call(target);
}
}

使用 Object.prototype.toString 配合闭包,通过传入不同的判断类型来返回不同的判断函数,一行代码,简洁优雅灵活(注意传入type传入参数时首字母大写)

不推荐将这个函数用来检测可能会产生包装类型的基本数据类型上,因为call会将第一个参数进行装箱操作

2、es5实现数组map方法

对于map方法的运用,我们可以在mdn中查看到对应的用法,如下

1
2
3
4
5
6
7
8
9
const selfMap = function(fn, context) {
let arr = Array.prototype.slice.call(this);
let mappedArr = [];
for (let i = 0; i< arr.length; i++) {
if (!arr.hasOwnProperty(i)) continue;
mappedArr.push(fn.call(context, arr[i], i, this)))
}
return mappedArr;
}

值得一提的是,map的第二个参数为第一个参数回调中的this指向,如果第一个参数为箭头函数,那设置第二个this会因为箭头函数的词法绑定而失败。
另外就是对稀疏数组的处理,通过hasOwnProperty来判断当前下标的元素是否存在与数组中

3.使用reduce实现数组map方法

1
2
3
4
5
6
7

const selfMap2 = function(fn, context) {
let arr = Array.prototype.slice.call(this);
return arr.reduce((pre,cur,index) => {
return [...pre,fn.call(context, cur, index, this)]
}, [])
}

4.ES5实现数组filter方法

1
2
3
4
5
6
7
8
9
const selfFilter = function(fn, context) {
let arr = Array.prototype.slice.call(this);
let filteredArr = [];
for (let i = 0; i< arr.length; i++) {
if (!arr.hasOwnProperty(i)) continue;
fn.call(context, arr[i], i, this) && filteredArr.push(arr[i]);
}
return filteredArr;
}

5.使用reduce实现数组filter方法

1
2
3
4
5
const selfFilter2 = function (fn, context){
return this.reduce((pre, cur, index) => {
return fn.call(context, cur, index, this) ? [...pre, cur] : [...pre]
}, [])
}

6. ES5实现数组的some方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

const selfSome = function(fn, context) {
let arr = Array.prototype.slice.call(this);
if (!arr.length) return false;
let flag = false;
for (let i = 0; i < arr.length; i++) {
if (!arr.hasOwnProperty(i)) continue;
let res = fn.call(context, arr[i], i, this);
if (res) {
flag = true;
break;
}
}
return flag;
}

7.ES5实现数组的reduce方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/**
@description 找到第一个empty的元素的下标
@param {Array} arr - 参看说明
@param {Number} {initIndex} - 遍历的起始下标
@return {Number}
*/

const findRealElementIndex = function(arr, initIndex) {
let index;
for (let i = initIndex || 0, i < arr.length; i++){
if (!arr.hasOwnProperty(i)) continue;
index = i;
return index;
}
}

const selfReduce = function(fn, initValue) {
let arr = Array.prototype.slice.call(this);
let res;
if (initValue === undefined) {
res= = arr[findRealElementIndex(arr)];
for (let i = 0; i < arr.length - 1; i++){
if (!arr.hasOwnProperty(i)) continue;
let realElementIndex = findRealElementIndex(arr, i+1);
res = fn.call(null, res, arr[realElementIndex], realElementIndex, this);
} else {
res = initValue;
for (let i = 0; i < arr.length; i++) {
if (!arr.hasOwnProperty(i)) continue;
res = fn.call(null, res, arr[i], i, this);
}
}
}
return res;
}

因为可能存在稀疏数组的关系,所以reduce实现略有点复杂,需要保证跳过稀疏数组,遍历正确的元素和下标

8.使用reduce 实现数组的flat方法

1
2
3
4
5
6
7
8
9
10
11
const selfFlat = function(depth = 1) {
let arr = Array.prototype.slice.call(this);
if (depth === 0) return arr;
return arr.reduce((pre, cur) => {
if (Array.isArray(cur)) {
return [...pre, ...selfFlat.call(cur, depth - 1)]
} else {
return [...pre, ...cur]
}
}, [])
}

因为slefFlat是依赖this指向的,所以在reduce遍历时需要指定slefFlat的this指向,否则会默认指向window从而发生错误。
远离通过reduce遍历数组,遇到数组的某个元素仍是数组,通过ES6的扩展运算符对其进行降维(ES5可以使用concat方法),而这个数组元素可能内部还嵌套数组,所以需要递归调用slefFlat
同时原生的falt方法支持一个depth参数表示较为的深度,默认为1即给数组降一层维度。

1
2
3
4
5
6
let arr = [1, 2, [3, 4], [5, [6, 7]]];
arr.flat();
// 返回为 [1, 2, 3, 4,5, [6, 7]]
// 传入Inifity 会将传入的数组变成一个一维数组
arr.falt(Infinity)
// [1, 2, 3, 4, 5, 6, 7]

原理是每递归一次将depth参数减1,如果depth参数为0时,直接返回原数组

9.实现ES6的classs语法

1
2
3
4
5
6
7
8
9
10
11
12
function inhertit(subType, superType) {
subType.prototype = Object.create(superType.prototype, {
contructor: {
enumerable: false,
configurable: true,
writable: true,
value: superType.contructor
}
})
// 继承递归
Object.setPrototypeOf(subType, superType);
}

ES6的class内部时基于寄生组合式继承,它是目前最理想的继承方式,通过Object.create方法创造一个空对象,并将这个空对象继承Object.create方法的参数,再让子类(subType)的原型对象等于这个空对象,就可以实现子类实例的原型等于这个空对象,而这个空对象的原型又等于父类原型对象(superType.peototype)的继承关系。

而Object.create支持第二个参数,即给生成的空对象定义属性和属性描述符/访问器描述符,我们可以给这个空对象定义一个contructor 属性更加符合默认的继承行为,同时它是不可枚举的内部属性(enumerable: false)

而ES6的class允许子类继承父类的静态方法和静态属性,而普通的寄生组合式继承只能做到实例与实例之间的继承,对于类与类之间的继承需要额外定义方法,这里使用Object.setPrototypeOf将superType设置为subType的原型,从而能够从父类中继承静态方法和静态属性。