原生JS灵魂之问(中),看看你是否熟悉JavaScript?

笔者最近在对原生JS的知识做系统梳理,因为我觉得JS作为前端工程师的根本技术,学再多遍都不为过。打算来做一个系列,一共分三次发,以一系列的问题为驱动,当然也会有追问和扩展,内容系统且完整,对初中级选手会有很好的提升,高级选手也会得到复习和巩固。这是本系列的第二篇。

扫了一眼目录后,也许你可能会说:这些八百年都用不到的东西,我为什么要会?是,我承认真实业务场景中并不会要你手写一个splice, 手写深拷贝或者V8的数组排序,但我要说的是,问这些问题的初衷并不是让你拿到平时去用的,而是检验你对 JS语言的理解有没有到达那样的水准,有一些 边界情况是否能够考虑到,有没有基本的 计算机素养(比如最基本的排序方法到底理不理解),未来有没有潜力去设计出更加优秀的产品或者框架。如果你仅仅是想通过一篇文章来解决业务中的临时问题,那不好意思,请出门左拐,这篇文章确实不适合你。但如果你觉得自己的原生编程能力还有待提高,想让自己的思维能力上一个台阶,希望我这篇"呕心沥血"整理了1w多字的文章能够让你有所成长。另外补充一句,本文并不针对面试,但以下任何一篇的内容放在面试中,都是非常惊艳的操作:)

第七篇: 函数的arguments为什么不是数组?如何转化成数组?

因为argument是一个对象,只不过它的属性从0开始排,依次为0,1,2...最后还有callee和length属性。我们也把这样的对象称为类数组。

常见的类数组还有:

  1. 用getElementByTagName/ClassName/Name()获得的HTMLCollection

  •  
  1. 用querySlector获得的nodeList

那这导致很多数组的方法就不能用了,必要时需要我们将它们转换成数组,有哪些方法呢?

1. Array.prototype.slice.call()

 
  1. function sum(a, b) {

  2. let args = Array.prototype.slice.call(arguments);

  3. console.log(args.reduce((sum, cur) => sum + cur));//args可以调用数组原生的方法啦

  4. }

  5. sum(1, 2);//3

2. Array.from()

 
  1. function sum(a, b) {

  2. let args = Array.from(arguments);

  3. console.log(args.reduce((sum, cur) => sum + cur));//args可以调用数组原生的方法啦

  4. }

  5. sum(1, 2);//3

这种方法也可以用来转换Set和Map哦!

3. ES6展开运算符

 
  1. function sum(a, b) {

  2. let args = [...arguments];

  3. console.log(args.reduce((sum, cur) => sum + cur));//args可以调用数组原生的方法啦

  4. }

  5. sum(1, 2);//3

4. 利用concat+apply

 
  1. function sum(a, b) {

  2. let args = Array.prototype.concat.apply([], arguments);//apply方法会把第二个参数展开

  3. console.log(args.reduce((sum, cur) => sum + cur));//args可以调用数组原生的方法啦

  4. }

  5. sum(1, 2);//3

当然,最原始的方法就是再创建一个数组,用for循环把类数组的每个属性值放在里面,过于简单,就不浪费篇幅了。

第七篇: forEach中return有效果吗?如何中断forEach循环?

在forEach中用return不会返回,函数会继续执行。

 
  1. let nums = [1, 2, 3];

  2. nums.forEach((item, index) => {

  3. return;//无效

  4. })

中断方法:

  1. 使用try监视代码块,在需要中断的地方抛出异常。

  2. 官方推荐方法(替换方法):用every和some替代forEach函数。every在碰到return false的时候,中止循环。some在碰到return ture的时候,中止循环

第八篇: JS判断数组中是否包含某个值

方法一:array.indexOf

此方法判断数组中是否存在某个值,如果存在,则返回数组元素的下标,否则返回-1。

 
  1. var arr=[1,2,3,4];

  2. var index=arr.indexOf(3);

  3. console.log(index);

方法二:array.includes(searcElement[,fromIndex])

此方法判断数组中是否存在某个值,如果存在返回true,否则返回false

 
  1. var arr=[1,2,3,4];

  2. if(arr.includes(3))

  3. console.log("存在");

  4. else

  5. console.log("不存在");

方法三:array.find(callback[,thisArg])

返回数组中满足条件的第一个元素的值,如果没有,返回undefined

 
  1. var arr=[1,2,3,4];

  2. var result = arr.find(item =>{

  3. return item > 3

  4. });

  5. console.log(result);

方法四:array.findeIndex(callback[,thisArg])

返回数组中满足条件的第一个元素的下标,如果没有找到,返回 -1]

 
  1. var arr=[1,2,3,4];

  2. var result = arr.findIndex(item =>{

  3. return item > 3

  4. });

  5. console.log(result);

当然,for循环当然是没有问题的,这里讨论的是数组方法,就不再展开了。

第九篇: JS中flat---数组扁平化

对于前端项目开发过程中,偶尔会出现层叠数据结构的数组,我们需要将多层级数组转化为一级数组(即提取嵌套数组元素最终合并为一个数组),使其内容合并且展开。那么该如何去实现呢?

需求:多维数组=>一维数组

 
  1. let ary = [1, [2, [3, [4, 5]]], 6];// -> [1, 2, 3, 4, 5, 6]

  2. let str = JSON.stringify(ary);

1. 调用ES6中的flat方法

 
  1. ary = arr.flat(Infinity);

2. replace + split

 
  1. ary = str.replace(/(\[|\])/g, '').split(',')

3. replace + JSON.parse

 
  1. str = str.replace(/(\[|\]))/g, '');

  2. str = '[' + str + ']';

  3. ary = JSON.parse(str);

4. 普通递归

 
  1. let result = [];

  2. let fn = function(ary) {

  3. for(let i = 0; i < ary.length; i++) {

  4. let item = ary[i];

  5. if (Array.isArray(ary[i])){

  6. fn(item);

  7. } else {

  8. result.push(item);

  9. }

  10. }

  11. }

5. 利用reduce函数迭代

 
  1. function flatten(ary) {

  2. return ary.reduce((pre, cur) => {

  3. return pre.concat(Array.isArray(cur) ? flatten(cur) : cur);

  4. }, []);

  5. }

  6. let ary = [1, 2, [3, 4], [5, [6, 7]]]

  7. console.log(flatten(ary))

6:扩展运算符

 
  1. //只要有一个元素有数组,那么循环继续

  2. while (ary.some(Array.isArray())) {

  3. ary = [].concat(...ary);

  4. }

这是一个比较实用而且很容易被问到的问题,欢迎大家交流补充。

第十篇: JS数组的高阶函数——基础篇

1.什么是高阶函数

概念非常简单,如下:

一个函数就可以接收另一个函数作为参数或者返回值为一个函数, 这种函数就称之为高阶函数。

那对应到数组中有哪些方法呢?

2.数组中的高阶函数

1.map

  • 参数:接受两个参数,一个是回调函数,一个是回调函数的this值(可选)。

其中,回调函数被默认传入三个值,依次为当前元素、当前索引、整个数组。

  • 创建一个新数组,其结果是该数组中的每个元素都调用一个提供的函数后返回的结果

  • 对原来的数组没有影响

 
  1. let nums = [1, 2, 3];

  2. let obj = {val: 5};

  3. let newNums = nums.map(function(item,index,array) {

  4. return item + index + array[index] + this.val;

  5. //对第一个元素,1 + 0 + 1 + 5 = 7

  6. //对第二个元素,2 + 1 + 2 + 5 = 10

  7. //对第三个元素,3 + 2 + 3 + 5 = 13

  8. }, obj);

  9. console.log(newNums);//[7, 10, 13]

当然,后面的参数都是可选的 ,不用的话可以省略。

2. reduce

  • 参数: 接收两个参数,一个为回调函数,另一个为初始值。回调函数中三个默认参数,依次为积累值、当前值、整个数组。

 
  1. let nums = [1, 2, 3];

  2. // 多个数的加和

  3. let newNums = nums.reduce(function(preSum,curVal,array) {

  4. return preSum + curVal;

  5. }, 0);

  6. console.log(newNums);//6

不传默认值会怎样?

不传默认值会自动以第一个元素为初始值,然后从第二个元素开始依次累计。

3. filter

参数: 一个函数参数。这个函数接受一个默认参数,就是当前元素。这个作为参数的函数返回值为一个布尔类型,决定元素是否保留。

filter方法返回值为一个新的数组,这个数组里面包含参数里面所有被保留的项。

 
  1. let nums = [1, 2, 3];

  2. // 保留奇数项

  3. let oddNums = nums.filter(item => item % 2);

  4. console.log(oddNums);

4. sort

参数: 一个用于比较的函数,它有两个默认参数,分别是代表比较的两个元素。

举个例子:

 
  1. let nums = [2, 3, 1];

  2. //两个比较的元素分别为a, b

  3. nums.sort(function(a, b) {

  4. if(a > b) return 1;

  5. else if(a < b) return -1;

  6. else if(a == b) return 0;

  7. })

当比较函数返回值大于0,则 a 在 b 的后面,即a的下标应该比b大。

反之,则 a 在 b 的后面,即 a 的下标比 b 小。

整个过程就完成了一次升序的排列。

当然还有一个需要注意的情况,就是比较函数不传的时候,是如何进行排序的?

答案是将数字转换为字符串,然后根据字母unicode值进行升序排序,也就是根据字符串的比较规则进行升序排序。

第十一篇: 能不能手动实现数组的map方法 ?

依照 ecma262 草案,实现的map的规范如下:

下面根据草案的规定一步步来模拟实现map函数:

 
  1. Array.prototype.map = function(callbackFn, thisArg) {

  2. // 处理数组类型异常

  3. if (this === null || this === undefined) {

  4. throw new TypeError("Cannot read property 'map' of null or undefined");

  5. }

  6. // 处理回调类型异常

  7. if (Object.prototype.toString.call(callbackfn) != "[object Function]") {

  8. throw new TypeError(callbackfn + ' is not a function')

  9. }

  10. // 草案中提到要先转换为对象

  11. let O = Object(this);

  12. let T = thisArg;

  13.  
  14.  
  15. let len = O.length >>> 0;

  16. let A = new Array(len);

  17. for(let k = 0; k < len; k++) {

  18. // 还记得原型链那一节提到的 in 吗?in 表示在原型链查找

  19. // 如果用 hasOwnProperty 是有问题的,它只能找私有属性

  20. if (k in O) {

  21. let kValue = O[k];

  22. // 依次传入this, 当前项,当前索引,整个数组

  23. let mappedValue = callbackfn.call(T, KValue, k, O);

  24. A[k] = mappedValue;

  25. }

  26. }

  27. return A;

  28. }

这里解释一下, length >>> 0, 字面意思是指"右移 0 位",但实际上是把前面的空位用0填充,这里的作用是保证len为数字且为整数。

举几个特例:

 
  1. null >>> 0 //0

  2.  
  3. undefined >>> 0 //0

  4.  
  5. void(0) >>> 0 //0

  6.  
  7. function a (){}; a >>> 0 //0

  8.  
  9. [] >>> 0 //0

  10.  
  11. var a = {}; a >>> 0 //0

  12.  
  13. 123123 >>> 0 //123123

  14.  
  15. 45.2 >>> 0 //45

  16.  
  17. 0 >>> 0 //0

  18.  
  19. -0 >>> 0 //0

  20.  
  21. -1 >>> 0 //4294967295

  22.  
  23. -1212 >>> 0 //4294966084

总体实现起来并没那么难,需要注意的就是使用 in 来进行原型链查找。同时,如果没有找到就不处理,能有效处理稀疏数组的情况。

最后给大家奉上V8源码,参照源码检查一下,其实还是实现得很完整了。

 
  1. function ArrayMap(f, receiver) {

  2. CHECK_OBJECT_COERCIBLE(this, "Array.prototype.map");

  3.  
  4. // Pull out the length so that modifications to the length in the

  5. // loop will not affect the looping and side effects are visible.

  6. var array = TO_OBJECT(this);

  7. var length = TO_LENGTH(array.length);

  8. if (!IS_CALLABLE(f)) throw %make_type_error(kCalledNonCallable, f);

  9. var result = ArraySpeciesCreate(array, length);

  10. for (var i = 0; i < length; i++) {

  11. if (i in array) {

  12. var element = array[i];

  13. %CreateDataProperty(result, i, %_Call(f, receiver, element, i, array));

  14. }

  15. }

  16. return result;

  17. }

参考:

V8源码

Array 原型方法源码实现大揭秘

ecma262草案

第十二篇: 能不能手动实现数组的reduce方法 ?

依照 ecma262 草案,实现的reduce的规范如下:

其中有几个核心要点:

1、初始值不传怎么处理

2、回调函数的参数有哪些,返回值如何处理。

 
  1. Array.prototype.reduce = function(callbackfn, initialValue) {

  2. // 异常处理,和 map 一样

  3. // 处理数组类型异常

  4. if (this === null || this === undefined) {

  5. throw new TypeError("Cannot read property 'reduce' of null or undefined");

  6. }

  7. // 处理回调类型异常

  8. if (Object.prototype.toString.call(callbackfn) != "[object Function]") {

  9. throw new TypeError(callbackfn + ' is not a function')

  10. }

  11. let O = Object(this);

  12. let len = O.length >>> 0;

  13. let k = 0;

  14. let accumulator = initialValue;

  15. if (accumulator === undefined) {

  16. for(; k < len ; k++) {

  17. // 查找原型链

  18. if (k in O) {

  19. accumulator = O[k];

  20. k++;

  21. break;

  22. }

  23. }

  24. // 循环结束还没退出,就表示数组全为空

  25. throw new Error('Each element of the array is empty');

  26. }

  27. for(;k < len; k++) {

  28. if (k in O) {

  29. // 注意,核心!

  30. accumulator = callbackfn.call(undefined, accumulator, O[k], O);

  31. }

  32. }

  33. return accumulator;

  34. }

其实是从最后一项开始遍历,通过原型链查找跳过空项。

最后给大家奉上V8源码,以供大家检查:

 
  1. function ArrayReduce(callback, current) {

  2. CHECK_OBJECT_COERCIBLE(this, "Array.prototype.reduce");

  3.  
  4. // Pull out the length so that modifications to the length in the

  5. // loop will not affect the looping and side effects are visible.

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值