ES6(递归、箭头函数、浅拷贝、深拷贝、Object、symbol、promise)

  • Post author:
  • Post category:其他




递归函数

递归函数本质上就是函数内部自调用,这时如果我们不加判断条件,那么函数就会不停地去调用,所以我们要给函数加上判断条件,用于跳出循环。

        // 递归 函数内部自调用,但是必须要有跳出循环的条件
        function back (n){
            if(n==5) {
                return 0;
            }{
                return back(n+1);
            }
        }
        console.log(back(3));   // 0
  • 斐波那契数列(兔子数列)

0,1,1,2,3,5,8…从第三个数开始,它的值等于前面两个值相加,这就是斐波那契数列

        function fn(n){
            if(n===1){
                return 0
            }else if(n===2){
                return 1
            }else{
                return fn(n-1) +fn(n-2);
            }
        }
        console.log(fn(4));

这种写法,单独判断了第一个数和第二个数的情况,虽然可以获取我们想要的结果,但是运算量比较大,我们还可以尝试以下的写法

        function getSum(n,curr=0,next=1){
            if(n===1){      // 这个条件用于跳出循环
                return curr;    
            }else{
                return getSum(n-1,next,curr+next);
            }
        }
        console.log(getSum(1));

这里输入的n就不是参与运算的参数了,而是充当了一个计数器的角色

  • 麦粒案例

这也是一个经典的案例,第一个格子有一粒麦子,第二个格子有2粒麦子,第三个格子有4粒麦子,后面一个的数量是前一个的两倍

        // 麦粒
        function getMIke(n){
            if(n===1){
                return 1
            }else{
                return Math.pow(2,n-1)+getMIke(n-1);   // 每一个格子的数量是 2的n次方-1
            }
        }
        console.log(getMIke(2));



箭头函数

  • 箭头函数省去了普通函数的function的声明,直接使用了这样的格式

    var fun = (参数)=>{代码}

    ,小括号里放要传入的参数,花括号里写执行的代码。
  • 当传入的参数有且只有一个时,小括号可以省略,其他情况都要加上小括号
  • 当执行的代码只有一句时,花括号也可以省去
  • 当执行的代码只有一句时,return也可以省去
        // 箭头函数
        var a = ()=>{console.log("这是一个箭头函数");}
        a();

        // 如果参数有且只有一个
        var b = aa=>{console.log("参数只有一个");}
        b();

        // 如果方法只有一句,{} 可以省略
        var c = ()=>console.log("方法只有一句");
        c();

        // 如果方法只有一句,return也可以省略
        var d = () => "你看return也可以省略";
        console.log(d());
  • 注意:箭头函数是没有this指向的,但是我们可以在箭头函数里使用this,这里的this就是指向它的父级了。
  • 我个人认为箭头函数没有this指向的含义指的是,没有this是指向箭头函数,即便里面写了this,也只是指向它的父级而已
// 箭头函数没有this指向,也就是说箭头函数中的this是不能指向它自己的,箭头函数中的this指向的是他的父级,
        window.document.getElementById("box").onclick = ()=>console.log(this);  // this指向window

        // 对象中的函数
        var obj = {
            name:"这是一个对象",
            // fn:function(){console.log(this);}    // 指向当前对象
            fn:()=>console.log(this)       // window
        }
        obj.fn();

        // 构造函数
        function fun (name){
            this.name=name;
            // this.aa = function(){console.log(this);}   // 指向实例
            this.aa = () => console.log(this);  // 目前结果上是指向实例
        }
        console.log(fun);
        var newA = new fun("张三");
        newA.aa();
        
        // 构造函数原型上添加方法
        // fun.prototype.addFun = function(){console.log(this);}   // 指向实例
        fun.prototype.addFun = ()=>console.log(this);   // 指向window
        newA.addFun()



浅拷贝、深拷贝

  • 在了解浅拷贝和深拷贝之前,我们再来简单地了解一下栈和堆
  • 栈是我们存放一些值类型数据的地方,而堆是我们用来存放引用类型数据的地方。
  • 比如说我们声明的一些变量名、字符串之类的就存放在栈里面,而这些变量名代表的对象、方法等是存- 放在堆里的,我们使用变量名的时候,就能够从堆里提取出相应的对象、方法等



浅拷贝

  • 浅拷贝是只拷贝一层数据,深层对象就拷贝它的地址
  • 浅拷贝之后,我们无论是改变拷贝值或者被拷贝的值,都会让数据同步地发生变化
        // 浅拷贝 只拷贝一层 深层对象只拷贝地址:栈里存放变量名,堆里存放引用数据类型  拷贝后的内容修改,原数据也会修改
        let obj1 = {
            name:"aa",
            age:5
        }
        let obj2 = obj1;   // 浅拷贝
        console.log(obj1);
        console.log(obj2);   // 打印的结果都是相同的
        obj2.name = "bb";
        console.log(obj1);
        console.log(obj2);
        obj1.name="cc";
        console.log(obj1);
        console.log(obj2);  



深拷贝

  • 深拷贝能够拷贝到每一层数据,我们无论修改拷贝前或者拷贝后的数据,都不会对对方产生影响
        // 深拷贝 每一层都拷贝,两个内容不会影响
        let obj3 = {
            name:obj1.name,
            age:obj1.age
        }
        console.log(obj3);
        obj3.name = "ee";
        console.log(obj3);
        console.log(obj1);

我们可以通过上面这种手动的方式来进行深层拷贝,但是在数据很多的时候是行不通的,我们还有其他的办法:

  1. 使用Object.assgin(),它可以将所有可枚举的属性值,从一个或者多个对象上分配到目标上
        // 第一种方法,Object.assgin() 用于将所有的可枚举的属性值,从一个或多个对象分配到目标上
        let obj4 = {
            name:"dd",
            age:15,
            time:new Date,
            reg:/^\d{10}$/
        }
        let obj5 = {};
        Object.assign(obj5,obj4);
        console.log(obj5);
  1. 使用JSON的方法

    但是使用它存在一些弊端,就是对象中使用了date对象时,时间上会存在一些偏差,它是直接复制时间的字符串,而不是去解析代码
        // 第二种方法,使用json  转换时间存在问题,将date转换为字符串
        let obj6 = JSON.stringify(obj4);
        obj6=JSON.parse(obj6);
        console.log(obj6);
  1. 使用递归

    这种方法相对来说是很复杂的,需要进行很多的判断。目前这个方法我认为可能还存在一些错误,也希望如果有人发现,可以多多指点,谢谢
  // 第三种方法 递归
        function deepC (e) {
            let a ;
            if (typeof e === "object") {
                // 判断是否为数组
                if(Array.isArray(e)){
                    a=[];
                    for(let i in e){
                        a.push(deepC(e[i]))
                    }
                }else if(e===null){
                // 判断是否为null
                    a=e
                }else if(e.constructor===RegExp){
                // 判断是否为正则
                a=e
                }else if(e.constructor === Function){
                // 判断是否为函数
                a=e
                }else{
                // 判断是否为对象
                    a = {};
                    for(let i in e){
                        a[i] = deepC(e[i])
                    }
                };
            }else{
                // 值类型数据直接赋值
                a=e;
            }
            return a;
        }



symbol数据类型

它是ES6新增的数据类型,代表独一无二的值,存储关键数据或者服务器数据

        // Symbol代表唯一的数据
        let a = Symbol("11");
        let b = Symbol("11");
        console.log(a==b);  // false
        console.log(a===b); // false  它们都是各自的唯一的值,是不相等的
  • description 获取symbol中的值
        // 获取symbol中的值  description
        console.log(a.description);
        console.log(a.description === b.description);   // true  这里的比较单纯是在比较两个值是否是相同的
  • for 用来指定一个symbol的空间
       // for 通过for来指定symbol的空间
       let c = Symbol.for("22");
       let d = Symbol.for("22");
       console.log(c===d);  //true  来源相同值也相同,所以返回true
  • keyFor 通过变量名,反向查找symbol数据,不过只对于用for声明出来的数据才起作用
        // keyFor 通过变量名反向 查找symbol数据,只有用for声明出来的变量才可以使用
        console.log(Symbol.keyFor(c));  // 22
        console.log(Symbol.keyFor(d));   // 22



Object对象

  • defineProperty(obj,prop,descriptor)用于重新定义对象的属性
        // Object.defineProperty(obj,prop,description)  重新定义对象
        let obj = {
            name:"aa",
            age:15,
            sex:"男"
        }
        console.log(obj);
        Object.defineProperty(obj,"age",{
            value:16,
        });
        console.log(obj);
  • delete obj.prop 删除对象的属性
        // 删除对象中的属性
        delete obj.sex;
        console.log(obj);
  • writable:true/false 规定定义的属性是否可以修改,默认是true
  • enumerable:true/false 规定定义的属性是否可以被遍历出来,默认为false
  • configurable:true/false 规定定义的属性是否可以被删除,默认为false
 let obj2 = {
            key1:1,
            key2:2,
            key3:3
        }
        Object.defineProperty(obj2,"key1",{
            value:111,
            writable:false,   // 禁止修改
        })
        console.log(obj2);
        obj2.key1 = 3333;
        console.log(obj2);  // 没有修改

        Object.defineProperty(obj2,"key2",{
            value:222,
            enumerable:false,   // 禁止遍历
        })
        for(let i in obj2){
            console.log(obj2[i]);
        }

        Object.defineProperty(obj2,"key3",{
            value:333,
            configurable:false,   // 禁止删除
        })
        delete obj2.key3;
        console.log(obj2);   // 不可删除
        delete obj2.key2;
        console.log(obj2);   // 可删除



Promise

ES6新增的构造函数,创造一个promise实例对象,代表将来要发生的事情,用来传递异步操作

Promise对象有3个状态

  1. pending 初始状态
  2. fulfilled 成功状态
  3. reject 失败状态

我们创建Promise对象时,promise构造函数是可以接收一个函数作为参数的,而这个函数有两个参数:resolve和reject。这两个参数是js原先已经设置好的函数,有着各自的作用。

  • resolve可以将初始状态变为成功状态,并能将一个值抛出去
  • reject可以将初始状态变为失败状态,并将一个值抛出去
        // Promise是构造函数,创造一个promise实例对象,代表将来要发生的事情,用来传递异步函数

        const pro = new Promise(function (resolve, reject) {
            let a = 0;  // 初始状态
            // console.log(a);
            if (true) {
                // 成功的状态
                a = 1;
                resolve(1)
            } else {
                // 失败的状态
                a = 2;
                reject(2)
            }
        })
        console.log(pro);
  • then()、catch()

    then()是当成功时执行的代码

    catch()是当失败时执行的代码

它们是promise原型链上的方法,可以进行连写,后面的回调函数可以使用上一个回调函数返回的结果

        // 链式编程,后面的回调函数可以使用上次返回的结果
        // then是成功时进行的操作,catch是失败时进行的操作
        pro.then(r=>{
            console.log(r);
             return r*3
        }).then(r=>console.log(r)).then(r=>console.log(r))

下面的案例用到了then和catch

我们从下面的案例中可以看到,当运行到了reject的时候,下面就直接进行了catch里的代码,说明当reject执行时,就将状态转变为了失败的状态,便去执行失败时的代码(catch的代码)

         pro.then(r => {
             console.log(r);
             return r * 4
         }).then(r => {
             console.log(r);
             return Promise.resolve(r);
         }).then(r => {
             console.log(r);
             return Promise.reject(r)   // reject表示失败时传的值
         }).then(r =>             // 由于之前是失败时抛出的状态,所以不会走这个流程
         { console.log(r) }
         ).catch(e => console.log(e))



回调地狱

回调函数里嵌套回调函数就叫回调地狱,可以看一下下面的案例

        // 地狱回调
        setTimeout(() => {
            console.log("这是第一句");
            setTimeout(() => {
                console.log("这是第二句");
                setTimeout(() => {
                    console.log("这是第三句");
                    setTimeout(() => {
                        console.log("这是最后一句");
                    }, 1000);
                }, 1000);
            }, 1000);
        }, 1000);

这样做会导致我们的代码可读性很差,后期不好维护,我们可以用Promise来解决这个问题

        // 以promise的形式进行
        function tryF(str) {
            var time = new Promise(function (r, e) {
                setTimeout(() => {
                    if (true) r(str)   // 操作成功时将str抛出去
                     else e("操作失败")    // 操作失败时返回
                }, 500);
            })
            return time
        };
        tryF("这是第一句").then(r => {
            console.log(r);   // 需要把r打印出来,否则是看不到结果的
            return tryF("这是第二句")
        }).then(r => {
            console.log(r);
            return tryF("这是最后一句");
        }).then(r => {
            console.log(r);   // 这一步也是必须的,否则最后一句话是打印不出来的
        })

我们还可以使用Promise封装一个ajax函数:

        // promise封装ajax
        function getData(url, type, data) {
            let pro = new Promise(function (r, e) {
                // 初始化
                let xml = new XMLHttpRequest;
                // 判断状态
                xml.onreadystatechange = function () {
                    if (this.readyState != 4) {
                        return;   // 如果准备状态不是4,那就不进行后面的操作
                    }
                    if (this.status === 200) {
                        // 成功时
                        r(this.response)
                    } else {
                        // 失败时
                        e(new Error(this.statusText))
                    }
                }
                // 设置请求
                xml.open(type, url);
                xml.responseType = "json"  // 返回的数据格式
                xml.setRequsetHeader("Accept", "application/json");  // 设置请求头
                xml.send(data);   // 发送请求
            })
            return pro;   // 返回数据
        }



总结

今天的学习,难点主要在Promise。并不太理解它的用法和机制,尤其是在利用它解决回调地狱的问题和封装ajax的问题上,不过我认为,这种问题必须要有实际的案例,只有运用上了才能很好地解决掉自己的问题,否则看再多的资料,也不能彻底地打消心里的疑惑,所以现在的策略就是背下来就好。



版权声明:本文为m0_66970189原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。