forEach 你真的懂了吗?

317 阅读5分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第3天,点击查看活动详情。(juejin.cn/post/705288… "juejin.cn/post/705288…

MDN 出发:

  • 语法
arr.forEach(callback(currentValue [, index [, array]])[, thisArg])
  • 返回值:undefiend

forEach() 被调用时,不会改变原数组,也就是调用它的数组 确切地说: forEach 不会直接改变调用它的对象,但是那个对象可能会被 callback 函数改变

应用:

1. 不对稀疏数组处理 (跳过)

image.png image.png

2. 替换for

image.png

3.使用 thisArg

        // 如果 thisArg 参数有值,则每次 callback 函数被调用时,
        // this 都会指向 thisArg 参数。如果省略了 thisArg 参数,
        // 或者其值为 null 或 undefined,this 则指向全局对象。
        // 按照函数观察到 this 的常用规则,callback 函数最终可观察到 this 值。
        
        function Counter() {
            this.sum = 0;
            this.count = 0;
        }

        Counter.prototype.add = function (array) {
            array.forEach(function (entry) {
                console.log("this:",this);//obj
                this.sum += entry;
                ++this.count;
            }, this);
           
        };
        // Counter.prototype.add = function (array) {
        //     array.forEach(function (entry) {
        //         console.log("this:",this);//或者其值为 null 或 undefined,this 则指向全局对象。按
        //         this.sum += entry;
        //         ++this.count;
        //     }, undefined);
           
        // };
        // Counter.prototype.add = function (array) {
        //     array.forEach(function (entry) {
        //         console.log("this:",this);//或者其值为 null 或 undefined,this 则指向全局对象。按
        //         this.sum += entry;
        //         ++this.count;
        //     }, null);
           
        // };

        const obj = new Counter();
        console.log(obj);
        obj.add([2, 5, 9]);
        console.log(obj.count);
        // 3 === (1 + 1 + 1)
        console.log(obj.sum);
        // 16 === (2 + 5 + 9)
  • 注意:如果使用箭头函数表达式来传入函数参数, thisArg 参数会被忽略,因为箭头函数在词法上绑定了 this 值。 (箭头函数里面的this 是绑定context的)
  
        function Counter() {
            this.sum = 0;
            this.count = 0;
        }
        Counter.prototype.add =  (array) => {
            array.forEach((entry) => {
                console.log("this:", this); //window 
                this.sum += entry;
                ++this.count;
            }, this);
           
        };

        const obj = new Counter();
        console.log(obj);//Counter {sum: 0, count: 0}
        obj.add([2, 5, 9]);
        console.log(obj.count);//0
        console.log(obj.sum);//0

4.如果数组在迭代时被修改了,则其他元素会被跳过。 (坑位:去除数组中的某个元素 前面文章有写)

image.png


        var words = ['one', 'two', 'three', 'four'];
        words.forEach(function (word) {
            console.log(word);  // one two four
            if (word === 'two') {
                words.shift();
            }
        });

        var words = ['one', 'two', 'three', 'three','four'];
        words.forEach(function (word,index) {
            if (word === 'three') {
                words.splice(index,1)
            }
        });
        console.log(words);//["one", "two", "three", "four"]
        var words1 = ['one', 'two', 'three', 'three','four'];
        for(let i=0;i<words1.length;i++){
            if(words1[i] == "three"){
                words1.splice(i,1)
                i--
            }
        }
        console.log(words1);//  ["one", "two", "four"]

5.扁平化数组:

 /**
         * Flattens passed array in one dimensional array
         *
         * @params {array} arr
         * @returns {array}
         */
        function flatten(arr) {
            const result = [];

            arr.forEach((i) => {
                if (Array.isArray(i))

                    // 递归    [4, 5, [6, 7], 8, 9]
                    // [ ] 最外层数组
                    //    [] -> [4,5]-> [[]] -> [6,7] ->[4,5,...[6,7],8,9]
                    result.push(...flatten(i));
                else
                    result.push(i);
            })

            return result;
        }

        console.log([4, 5, ...[6, 7], 8, 9]); //[4, 5, 6, 7, 8, 9]

ps:

疑问: forEach如何去改变一个数组的值呢? image.png

forEach不改变原数组指的是不对原数组重新 赋值

  • 再看看这种:

image.png

  // 数组改值
        let arr = [1, 3, 5, 7, 9];
        arr.forEach(function (item) {
            item = 30;
        })
        console.log(arr); //输出 (5) [1, 3, 5, 7, 9]      
        // 数组改值
        let arr1 = [1, 3, 5, 7, 9];
        arr1.forEach(function (item, index, arr) {
            arr[index] = 30;
        })
        console.log(arr1); //输出 (5) [30, 30, 30, 30, 30]


         const arr3 = [{
            a: 1,
            b: 2,
        }, {
            a: 3,
            b: 4,
        }]
        arr3.forEach(item =>{
            if(typeof  item  === "object"){
                item.a = '54545'
            }
        })
        console.log(arr3);

发现arr里面的元素 都会被修改 !!!

arr[i] 是指直接修改了堆内存中的值 但是这个内存地址没有改变 还是原来的只是 原来堆内存中的内容被修改了而已

JavaScript是有基本数据类型与引用数据类型之分的。对于基本数据类型:number,string,Boolean,null,undefined它们在栈内存中直接存储变量与值。而Object对象的真正的数据是保存在堆内存,栈内只保存了对象的变量以及对应的堆的地址,所以操作Object其实就是直接操作了原数组对象本身。

  • so:引用数据类型通过arr[i]可以随便改
   arr.forEach((item,index) => {
            arr[index] = 'sdsakdjasjd'
        })

        console.log(arr);//["sdsakdjasjd", "sdsakdjasjd", "sdsakdjasjd", "sdsakdjasjd"]

但是这种通过forEach遍历修改值做法是并不推荐的 一般是循环次数未知 或者难以计算的时候 我们会去使用forEach 修改值还是用 map 或者for 循环 性能更好一些

7. break 报错

image.png

8. 声明式编程(Declarative Programming)和命令式编程(Imperative Programming)是两种编程范式

  • 8-1. 声明式编程就像是你告诉你朋友画一幅画,但是你不用去关心他怎么画。
  • 8-2. 命令式编程就像是你的朋友完全按照你的命令,将画一步步地画出来。

for 循环跟 forEach 都是属于命令式编程

var numbers = [1,2,3,4,5]
var doubled = []
for(var i = 0; i < numbers.length; i++) {
  var newNumber = numbers[i] * 2
  doubled.push (newNumber)
}
console.log (doubled) //=> [2,4,6,8,10]

map 声明式编程 :侧重于我们要求机器怎么去执行 拿到我们需要的东西

  • 举例:
var numbers = [1,2,3,4,5]
var doubled = []
for(var i = 0; i < numbers.length; i++) {
  var newNumber = numbers[i] * 2
  doubled.push (newNumber)
}
console.log (doubled) //=> [2,4,6,8,10]
var numbers = [1,2,3,4,5]
var doubled = numbers.map (function (n) {
  return n * 2
})
console.log (doubled) //=> [2,4,6,8,10]

map函数所做的事情是将直接遍历整个数组的过程归纳抽离出来,让我们专注于描述我们想要的是什么(what) 。注意,我们传入map的是一个纯函数;它不具有任何副作用(不会改变外部状态),它只是接收一个数字,返回乘以二后的值。

为什么我们需要去学习这种看起来有些怪异的归纳抽离出来的函数工具?

在很多情况中,命令式编程很好用。当我们写业务逻辑,我们通常必须要写命令式代码,没有可能在我们的专
项业务里也存在一个可以归纳抽离的实现。但是,如果我们花时间去学习(或发现)声明式的可以归纳抽离的部
分,它们能为我们的编程带来巨大的便捷。首先,我可以少写代码,这就是通往成功的捷径。而且它们能让我
们站在更高的层面是思考,站在云端思考我们想要的是什么,而不是站在泥里思考事情该如何去做。

需要明了的几个点:

  1. forEach无法return和break。

  2. forEach虽然是for的简化版, 但并不意味着它就比for好用,forEach适用于循环次数未知,或者计算循环次数比较复杂的时候用。

  3. 编程思想上来说,forEach属于命令式编程,map属于声明式编程。