Javascript核心开发解密

三种基础数据结构

  • 栈是一种数据结构,它是表达数据的一种存取方式。

    栈可以用来规定代码的执行顺序,在JavaScript中叫作函数调用栈(call stack),它是根据栈数据结构理论而实现的一种实践。

    存取的方式特点:先进后出,后进先出(LIFO,Last In,First Out)。

    push:向数组末尾添加元素(进栈方法)。

    	var a=[];
            a.push(1);//a:[1]
            a.push(2,4,6)//a:[1,2,4,6]
            var l=a.push(5)//a:[1,2,4,6,5]
            console.log(a);
    

    pop:弹出数据最末尾的一个元素(出栈方法)。

    	var a=[2,4,6,7,8];
            a.pop();//a:[2,4,6,7,8]
            console.log(a);
    
  • 堆数据结构通常是一种数状态结构。

    可以用用对象字面量对象的形式体现出来

    	var test = {
                a: 10,
                b: 20,
                c: {
                    m: 100,
                    n: 110
                }
            }
    

    示意图的理解方式

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ctcvcict-1604546278779)(C:\Users\19752\AppData\Roaming\Typora\typora-user-images\image-20201102203626430.png)]

  • 队列

    队列(queue)是一种先进先出(FIFO)的数据结构。正如排队过安检一样,排在队伍前面的人一定是最先过安检的人。原理图如下[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VJJYN4MD-1604546278781)(C:\Users\19752\Desktop\u=4007848708,1685442224&fm=15&gp=0.png)]

内存空间

  • 基本数据类型与变量对象

    类型
    Boolean只有两个值:true与false
    Null只有一个值:null
    Undefined只有一个值:undefined
    Number所有的数字
    String所有的字符串
    Symbol符号类型var sym=Symbol(‘testsymbol’)

    注意:目前很多浏览器还不支持Symbol,因此不建议在实践中使用。

  • 引用数据类型与堆内存空间

    引用数据类型(Object)的值是保存在堆内存空间中的对象。在JavaScript中,不允许直接访问堆内存空间中的数据,因此不能直接操作对象的堆内存空间。在操作对象时,实际上是在操作对象的引用而不是实际对象。因此,引用数据类型都是按引用访问的。这里的引用,可以理解为保存在变量对象中的一个地址,该地址与堆内存中的对象相关联。

    function foo() {
                var a1 = 10;
                var a2 = 'hello';
                var a3 = null;
                var b = { m: 20 };
                var c = [1, 2, 3];
            }
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-I9DRcedX-1604546278782)(C:\Users\19752\AppData\Roaming\Typora\typora-user-images\image-20201104000222968.png)]

    两个内存面试题

    	var a=20;
            var b=a;
            b=30;
          //这时候a的值是多少  
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OWyuNjue-1604546278784)(C:\Users\19752\AppData\Roaming\Typora\typora-user-images\image-20201104001504053.png)]

     var m={a:10,b:20};
            var n=m;
            n.a=15;
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kANNsRPW-1604546278787)(C:\Users\19752\AppData\Roaming\Typora\typora-user-images\image-20201104002312011.png)]

  • 内存空间管理

    	var num=20;
    	alter(num+1);
            num=null;
    
    1. 分配内存
    2. 使用分配到的内存
    3. 不需要时释放内存
    4. Javascript的垃圾回收实现主要依靠“引用”的概念,当一块内存空间中的数据能够被访问时,垃圾回收器就认为“该数据能够被获得”。不能够获得的数据,就会被打上标记,并回收内存空间。这种方式叫做 标记-清除算法

执行上下文

  • JavaScript运行环境
    1. 全局环境:代码运行起来首先会进入全局环境。
    2. 函数环境:当前函数被调用执行时,会进入当前函数中执行代码。
    3. eval环境:不建议使用。

变量对象

变量对象的创建过程

  1. 在chrome浏览中,变量对象会首先获得函数的参数变量及其值;在firefox浏览器中,是直接将参数对象arguments保存在变量对象中。
  2. 依次获取当前上下文中所有的函数声明,也是使用function关键字声明的函数。在变量对象中会以函数名建立一个属性,属性值为指向该函数所在的内存地址引用。如果函数名的属性已存在,那么该的属性值会被新的引用覆盖。
  3. 依次获取当前上下文中的变量声明,也就是使用var关键字声明的变量,每找到一个变量声明,就在变量对象中就以变量名建立一个属性,属性值为undefined。如果该变量名的属性已存在,为了防止同名的函数被修改为undefined,则会直接跳过,原属性值不会被修改。

执行过程

	var a=10;
		//创建阶段
        var a=undefined;
        a=10;

闭包

闭包的概念

  1. 闭包是一种特殊的对象。

  2. 它由两部分组成-执行上下文(代号A),以及在该执行上下文中创建的函数(代号B)。当B执行时,如果访问了A中的变量对象中的值, 那么闭包就产生了

    // 这就是一个简单的闭包
    		function foo(){
                var a=10;
                var b=20;
                function bar(){
                    return a+b;
                }
                return bar;
            }
    

    闭包的作用

    1. 闭包就是将某个功能集合通过函数的封装成一个模块,对外暴露一个借口,只能通过此接口去访问,闭包中的其他成员对于其他模块是隐藏的
    2. 限制了作用域,减少命名冲突
    3. 封装一个通用的功能供多模块使用,不用担心各个模块内出现命名冲突
    4. 由于变量常驻内存,增大内存使用量
    5. 就我个人使用过程中的理解,闭包就是将某个功能集合通过函数封装成一个模块,对外暴露一个接口,只可以通过此接口去访问,闭包中的其他成员对于其他的模块是隐藏的,限制了作用域,减少了名称冲突的发生.

    判断闭包的标准

    1. 必须有外部的封闭函数,该函数至少被调用一次
    2. 封闭的函数至少返回一个内部函数(对象字面量,{key:value…} )
    3. 闭包一定具有嵌套函数
    4. 内层函数一定操作了外层函数的局部变量
    5. 外层函数将内层函数返回外部,需要一个变量接住(全局变量)

闭包与垃圾回收机制

​ 当一个函数的执行上下文完毕之后,内部的所有内容都会失去引用而被垃圾回收机制回收。

​ 闭包的本质就是在函数外部保持了内部变量的引用,因此闭包会阻止垃圾回收机制进行回收。

	function fn() {
            var n = 999;
            add = function () {
                n += 1;
            }
            return function fn1() {
                console.log(n);
            }
        }
        var result = fn();
        result();
        add();
     result();

闭包与作用域链

	var fn=null;
        function foo(){
            var a=2;
            function innerFoo(){
                console.log(a);
            }
            fn=innerFoo;//将innerFoo的引用赋值给全局变量中fn
        }
        function bar(){
            fn();//此处保留了innerFoo得引用
        }
        foo();
        bar();//2

应用闭包

	//题目
	for (var i = 1; i <= 5; i++) {
            setTimeout(function () {
                console.log(i);
            }, i * 1000);
        }
		
        for (var i = 1; i <= 5; i++) {
            setTimeout(((i) => {
                return function () {
                    console.log(i);
                }
            })(i), i * 1000)
        }

        for (var i = 0; i < 5; i++) {
            (function (i) {
                setTimeout(function () {
                    console.log(i);
                }, i * 1000);
            })(i)
        }

单例模式与闭包

var per = {
            name: "iskr",
            age: 20,
            getName: function() {
                return this.name;
            },
            getAge: function() {
                return this.age;
            }
        }
        per.name = "敖";
        console.log(per.getName()); //敖

有私有方法属性的单例模式

//有私有方法、私有属性的单例**别人可以取可以修改,但是权限由我们决定的
var per = (function() {
            var name = "javascript";
            var age = 20;
            return {
                getName: function() {
                    return name;
                },
                getAge: function() {
                    return age;
                }
            }
        })();
        // per.getName = "13"; //报错
        console.log(per.getName());//访问私有的变量

闭包与模块化

 var module_status = (function() {
            var status = {
                number: 0,
                color: null,
            }
            var get = function(prop) {
                return status[prop];
            }
            var set = function(prop, value) {
                status[prop] = value;
            }
            return {
                get: get,
                set: set
            }
        })();

        var module_color = (function() {

            // 想当于 import status from 'module_status';
            var state = module_status;

            var colors = ['orange', 'red', 'pink'];

            function render() {
                var color = colors[state.get('number') % 3];
                document.body.style.backgroundColor = color;
            }
            return {
                render: render
            }

        })();

        var module_conext = (function() {
            var state = module_status;

            function render() {
                document.body.innerHTML = "this Number is" + state.get('number');
            }
            return {
                render: render
            }
        })();

        var module_main = (function() {
            var state = module_status;
            var color = module_color;
            var conext = module_conext;

            setInterval(function() {
                var newNumber = state.get('number') + 1;
                state.set('number', newNumber);
                color.render();
                conext.render();
            }, 1000)
        })();

this

当前函数的this是在函数被调用执行的时候才能确认的。

var a1 = 10;
        var obj1 = {
            a1: 20
        }

        function fn() {
            console.log(this.a1);
        }
        fn(); //  window
        fn.call(obj1); //改变指向并执行20   改变了this指向,obj
        fn.bind(obj1); //改变执行并未执行
        fn.apply(obj1); //改变指向并执行20
        //bind:语法和call一模一样,区别在于立即执行还是等待执行,bind不兼容IE6~8
        //apply:和call基本上一致,唯一区别在于传参方式
        //call(this,Number,Number)
        //apply(this,[Number,Number])第二个参数以数组的形式传参
        //bind方法 函数不会立即执行,而是返回一个新的函数,这个新的函数与原函数有共同的函数体
        //但它并非原函数,并且新的函数的参数与this指向都已经被绑定

注意的地方

function fn1() {
            // 'use strict'
            console.log(this); //在严格模式下输出undefined,非严格模式下this指向undefinde时它会自动指向全局对象
        }
        fn1(); //调用者为fn  *独立调用*  this为undefined
        window.fn1(); //fn是调用者,被window所拥有,this为window对象

独立调用时

 //demo2
        var a2 = 20;
        var obj2 = {
            a: 40
        }
        function fn2() {
            console.log("fn2 this:" + this);

            function foo() {
                console.log(this.a2);
            }
            foo(); //独立调用
        }
        fn2.call(obj2); //改变指向obj2
        fn2(); //独立调用 所以其值就会是全局作用域下的a2

对象字面量不会产生自己的作用域

//demo3
        var a3 = 20;

        function fn3() {
            // "use strict"; //严格模式必须放在头上最最最最上面
            var a3 = 1;
            var obj3 = { //对象字面量的写法不会产生自己的作用域
                a3: 10, //所以下面的a3就不会指向a3=10,this跟foo函数内部的this一样
                c3: this.a3 + a3
            }
            return obj3.c3;
        }
        console.log(window.fn3()); //指向全局
        console.log(fn3()); //独立调用在严格模式下报错this指向undefind

这里将函数引用的地址赋值给test4,然后独立调用

//demo4
        var a4 = 20;
        var fn4 = {
            a4: 10,
            getA: function() {
                return this.a4;
            }
        }
        console.log(fn4.getA()); //10
        var test4 = fn4.getA; //因为test4的指向为undefined非严格模式下指向window
        console.log(test4()); //则输出20

对象调用

//demo5
        function fn5() {
            console.log(this.a5);
        }

        function active(fn5) {
            fn5();
        }
        var a5 = 5;
        var obj5 = {
            a5: 55,
            getA1: fn5,
            active: active
        }
        active(obj5.getA1); //独立调用this为undefined  执行结果为5
        obj5.active(obj5.getA1); //5
 //demo6
        var n = 'window';
        var obj6 = {
            n: 'obj6',
            getN: function() {
                return function() {
                    return this.n;
                }
            }
        }
        console.log(obj6.getN()());//window

函数与函数式编程

函数声明

//1.函数声明
        function fn() {
            console.log("fn函数声明");
        }
        //在变量对象的创建过程中,function声明的函数比var声明的变量有更加优先的执行顺序,即我们常常提到的函数声明提前,
        //因此在同一执行上下文中,无论在什么地方声明了函数,都可以直接使用。
        function fn1() {
            var a = 20;

            function bar() {
                var b = 10;
                return a + b;
            }
            return bar();
        }
        console.log(fn1()); //30

函数表达式

//2.函数表达式
        //函数表达式其实就是将一个函数体赋值给一个变量的过程
        var fn2 = function() {};
        //因此,我们理解函数表达式时,可以与变量共同理解。上面代码的执行过程
        // var fn2=undefined;
        // fn2=function(){}
        // 其中,var fn2=undefined会因为变量对象的原因而提前执行,这就是常常被提到的变量提升。
        //因此当我们使用函数表达式时,必须要考虑代码执行的先后顺序的关系,这是与函数声明不同的地方
        //例
        // fn3();//fn3 is not function
        // var fn3 = function() {
        //     console.log("fn3")
        // }
        //如果喜欢使用函数表达式,那一定要有一个良好的编码习惯,以规避变量提升带来的负面影响。
        //更多的函数体赋值  场景
        function Person(naem) {
            this.name = name;
            this.age = age;
            // 在构造函数内部添加方法
            this.getAge = function() {
                return this.age;
            }
        }
        // 给原型上添加方法
        Person.prototype.getName = function() {
                return this.name;
            }
            // 在对象中添加方法
        var a = {
            m: 20,
            getM: function() {
                return this.m;
            },
            getThis: function() {
                return this;
            }
        }
        console.log(a.getThis()); //this指向调用者

匿名函数

//3.匿名函数
        //顾名思义,匿名函数就是没有名字的函数,一般会用作一个参数或者作为一个返回值来使用,通常来保存它的引用
        //(1)setTimeout中的参数
        setTimeout(function() {
            console.log("作为一个参数来使用")
        }, 1000);
        //(2)数组方法中的参数
        var arr = [1, 4, 6, 9];
        var arr1 = arr.map(function(item) {
            return item + 1
        });
        console.log(arr1); //map 返回一个新的数组  不会改变原始数组
        arr.forEach(function(item) {
            console.log(item);
        });
        //(3)匿名函数作为一个返回值
        function add() {
            var a3 = 10;
            return function() {
                return a3 + 10;
            }
        }
        console.log(add()());
        // 在实际开发中,当匿名函数作为一个返回值时,为了方便调试,常常会为匿名函数添加一个名字,这样在开发者工具中更好调试

        //注意:匿名函数和闭包!匿名函数是函数,函数是闭包的形成条件,仅仅有点关联

自执行函数

//4.自执行函数
        //自执行函数就是匿名函数一个非常重要的应用场景。因为函数会产生独立的作用域,
        //因此我们常常利用自执行函数来模拟块级作用域,并进一步在此基础上实现模块化的运用
        // (function(){})()
        //模块化很重要 自执行函数能更好的体现  模块可以包括:私有变量、私有方法、公有方法、公有变量、公有方法。
        //当我们使用自执行函数创建一个模块时,也意味着。外界已经无法访问该模块内部的任何属性和方法,
        //好在闭包,作为模块化之间能相互交流的桥梁,让模块化能够在我们的控制之下,选择性对外公开属性和方法
        (function() {
            // 私有变量量
            var age = 20;
            var name = "iskr";
            // 私有方法
            var getName = function() {
                return '你的名字叫' + name + '对吗?';
            };
            // 公有方法
            function getAge() {
                return age;
            }
            // 将引用保存在外部执行环境中,这是一种简单对外开发方法的方式
            window.getAge = getAge;
        })();
        //使用者   
        var demo = window.getAge();
        console.log(demo);

函数式编程

// 函数式编程
        //当我们想使用一个函数时,其实就是想将一些功能、逻辑等封装起来以便使用。
        function arrCount(arr) {
            var result = 0; //不赋值的话等于undefined  
            for (let i = 0; i < arr.length; i++) {
                result += arr[i]; //undefined+Number=NaN
            }
            return result; //然后结果就为NAN 
        }
        var arr8 = [4, 5, 8, 2, 9];
        console.log(arrCount(arr8));

        //把逻辑封装起来
        function getNumber(arr) {

            var res = [];
            arr.forEach(function(item) {
                if (typeof item === 'number') {
                    res.push(item);
                }
            })
            return res;
        }
        // 以上是封装,以下是功能实现
        var arrfn = [2, 4, 7, 'u', 3, 'i', 0];
        var res = getNumber(arrfn);
        console.log(res);
        // 在函数式编程实践中,我们封装了一个工具方法,专门用来找出一个数组中的所有number。
        //而我们真正需要维护的代码则仅仅只有两行。我们只需知道getNumber这个工具方法能做什么即可,
        // 而不关心它的内部实现。这样代码就会简洁很多,维护起来也非常简单。
        // 好比:在现实生活中很多场景一样,例如点外卖。只需要在App下单,然后等外卖小哥把食物送到我们手中就行。
        // 我们不关注如何做出来的,也不关注外卖小哥如何送过来的。这种更关心结果的思维方式,就是函数式编程。

函数是一等公民

        // 函数是一等公民
        // 所谓的"一等公民",其实就是普通公民,也就是说,函数其实没什么特殊的,我们可以像对待任何其他数据类型一样一样对待函数。
        // 可以把函数赋值给一个变量
        var fn8 = function() {};
        // 也可以把函数存在数组里。
        function fn9(callback) {
            var a9 = 20;
            return callback(20, 30) + a9;
        }

        function add9(a9, b9) {
            return a9 + b9;
        }
        fn9(add9); //70
        // 还可以把函数作为另一个函数运行的返回值
        function add10(x) {
            var y = 10;
            return function() {
                return x + y;
            }
        }
        var _add = add10(100);
        console.log(add10(100)()); //110

        function delay() {
            console.log("5000ms后执行")
        }
        var t = setTimeout(function() {
            delay()
        }, 5000); //糟糕的写法
        var t1 = setTimeout(delay, 5000); //正确的写法

思考下面代码

// function getUser(path, callback) {
        //     // return $.get(path, function(info) {//有被包裹
        //     //     return callback(info);
        //     // })

        //     return $.get(path, callback);//有被包裹
        // }
        // 直接简化成
        // var getUser = $.get();

        // getUser('/api/user', function(res) {
        //     console.log(res);
        // });

        // 等同于
        function add_ab(a11, b11) {
            return a11 + b11;
        }
        var other = add_ab;
        console.log(other(10, 5))

纯函数

// 纯函数
        // 相同的输入总会得到相同的输出,并且不会产生副作用的函数,就是纯函数
        // 此方法改变了其值 得到了一个新的值时 ,这是就不是一个纯函数
        var soure = [1, 2, 3, 4, 5];

        soure.slice(1, 3); //纯函数返回的是 【2,3】 soure不变
        // 不纯函数会改变其soure的值
        soure.splice(1, 3); //不纯函数
        soure.pop(); //删除数组的最后一个元素
        soure.push(6); //在元素末尾位置添加一个元素
        soure.shift(); //删除并返回第一个元素,
        soure.unshift(); //在数组开头添加一个元素
        soure.reverse(); //返回数组顺序
        // 纯函数的话不会改变soure的值
        var soure = [1, 2, 3, 4, 5];
        soure.concat([6, 7]); //连接两个数组
        soure.join('-'); //将数组的所有元素放入一个字符串
        //纯函数的可移植性
        // 没有改变原字符串的值,并输出了我们想要的结果
        function getParams(url, params) {
            if (!/\?/.test(url)) {
                return null;
            }

            var search = url.split("?")[1]; //将字符串分割成数组
            var arr = search.split('&');

            for (let i = 0; i < arr.length; i++) {
                var tmp = arr[i].split("=");
                if (tmp[0] === params) {
                    return decodeURIComponent(tmp[1]);
                }
            }
            return null;
        }

        var url = "https://www.bilibili.com/?spm_id_from=333.788.b_696e7465726e6174696f6e616c486561646572.1&name=iskr&nv=男";
        var str = getParams(url, 'name');
        console.log(str); //可以查看开发者工具理解代码执行过程
        // 纯函数的可缓存性

        function process(date) {
            var result = '';


            return result;
        }

        function withProcess(base) {
            var cache = {};
            return function() {
                var date = arguments[0];
                if (cache[date]) { //判断数据是否在缓存里面,如果再缓存里,直接返回出去这个值
                    return cache[date];
                }
                return base.apply(base, arguments); //如果缓存里没有这个值则就在请求一次方法
            }
        }

        var _process = withProcess(process);
        _process('2020-10-20');
        _process('2020-10-21');
        console.log(_process('2020-10-21'));
        console.log(_process('2020-10-21'));

高阶函数

//高阶函数
        function person1(name1, age1) {
            this.name1 = name1;
            this.age1 = age1;
        }
        person1.prototype.getName1 = function() {
            return this.name1;
        }
        var p1 = new person1("iskr", 18);
        console.log(p1.getName1());
        // 构造函数其实就是普通函数,而this是在函数运行的时候才确认的。
        // new导致了构造函数变得特别
        // 模拟关键字new的能力
        // 将构造函数以参数形式传入
        function New(fnc) {
            // 声明一个中间对象,该对象为最终返回的实例
            var res = {};
            if (fnc.prototype !== null) {
                // 将实例的原型指向构造函数的原型
                res.__proto__ = fnc.prototype;
            }
            // ret 为构造函数执行的结果,这里是通过apply,
            // 将构造函数内部的this指向修改为res,即为实例对象
            var ret = fnc.apply(res, Array.prototype.slice.call(arguments, 1)); //传递this指向,参数以数组的方式传递
            //当在构造函数中明确指定了返回对象时,那么new的执行结果就是该返回对象
            if ((typeof ret === 'object' || typeof ret === 'function') && ret !== null) {
                return ret;
            }
            // 如果没有明确指定的返回对象,则默认返回res,这个res就是实例对象

            return res;

        }
        // 模拟使用New方法
        var pe1 = New(person1, "iskr", 18);
        var pe2 = New(person1, "实例", 16);
        console.log(pe1.getName1());
        console.log(pe2.getName1());
        // 凡事接收一个函数作为参数的函数就是高阶函数

map方法封装的过程

// 数组map方法封装的思考过程
        var array1 = [1, 2, 3, 4]; //声明一个被遍历的数据array1
        // map方法第一个参数为回调函数,该函数拥有三个参数
        // 第一个参数表示array1数组中的每一项
        // 第二个参数表示当前遍历的索引值
        // 第三个参数表示数组本身
        // 该函数中的this指向map方法的第二个参数,若参数不存在,则thi指向丢失就是undefined
        var newArray1 = array1.map(function(item, i, array1) {
            console.log(item, i, array1, this);
            return item + 1;
        }, {
            a: 1 //指向为{a:1}
        }); //为新的一个数组,由map遍历结果组成,不是纯函数
        console.log(newArray1);
        //利用高阶函数的方式来封装_map方法功能和map一样
        Array.prototype._map = function(fn, context) {
            // 首先定义一个数组来保存每一项的运算结果,最后返回
            var temp = [];
            if (typeof fn === 'function') {
                var k = 0;
                var len = this.length;
                // 封装 for 循环过程
                for (; k < len; k++) {
                    // 将每一项的运算操作丢进fn里,
                    // 利用call方法指定fn的this指向与具体参数
                    temp.push(fn.call(context, this[k], k, this))
                }
            } else {
                console.error("报错:" + fn);
            }
            return temp;
        }
        var narray = [1, 2, 3, 4]._map(function(item) {
            return item + 1;
        })
        console.log(narray);

高阶函数的应用思路

// 高阶函数的使用思路正在于此,它其实是一个封装*公共逻辑*的过程。
        // 强化模块化思维  demo
        // 封装一个高阶函数withLogin,用来判断当前用户状态
        (function() {
            // 用随机数的方式来模拟一个获取用户信息的方式
            var getLogin = function() {
                var a = parseInt((Math.random() * 10).toFixed(0)); //0-10的随机数
                if (a % 2) { //模2不为零的话return
                    return {
                        login: false
                    }
                }
                return {
                    login: true,
                    userinfo: {
                        uname: 'iskr',
                        vip: 8
                    },
                    userid: '666666'
                }
            }
            var withLogin = function(basivFn) {
                    var loginInfo = getLogin();
                    // 将loginInfo以参数的形式传入基础函数中
                    return basivFn.bind(null, loginInfo); //loginInfo函数参数   把return的参数给renderIndex
                }
                // 定义接口供外面调用
            window.withLogin = withLogin;
        })();
        (function() {
            // 使用上面定义好的接口
            var withLogin = window.withLogin;

            var renderIndex = function(loginInfo) {
                    // 在这里处理Index页面的逻辑
                    if (loginInfo.login) {
                        // 处理登录之后的逻辑
                        console.log("登录成功");
                    } else {
                        // 处理未登录的逻辑
                        console.log("未登录");
                    }
                }
                // 对外暴露接口是,使用高阶函数包一层,来判断当前用户的登录状态
            window.renderIndex = withLogin(renderIndex);
        })();
        (function() {
            window.renderIndex();
        })();
        // 当我们使用高阶函数封装每一个页面的公共逻辑之后,会发现我们的代码逻辑变得非常清晰,而且更加统一。
        // 当在写新的页面逻辑是,就在此基础之上完成即可,二部用去考虑封装过的逻辑
        // 柯里化
        // 柯里化是指这样一个函数(假设叫做createCurry),它接收函数A作为参数,
        // 运行后能够返回一个新的函数,并且这个新的函数能够处理函数A的剩余参数
        // 把事情弄复杂化了,但是复杂化的同时使函数更加的自由化

        //##代码组合  回顾高阶函数的应用
        (function() {
            var env = {
                isMobile: false,
                isAndroid: false,
                isIOS: false
            }
            console.log(navigator.userAgent);
            var ua = navigator.userAgent;
            env.isMobile = 'ontouchstart' in document;
            env.isAndroid = !!ua.match(/android/);
            env.isIOS = !!ua.match(/iphone/);
            var withEnvironment = function(basicFn) {
                return basicFn.bind(null, env);
            }
            window.withEnvironment = withEnvironment;
        })();
        // ES6 ...args对参数不固定的写法
        function compose(...args) {
            var arity = args.length - 1;
            var tag = false;
            if (typeof args[args] === 'funciton') {
                tag = true;
            }
            if (arity > 1) {
                var param = args.pop(args[arity]);
                arity--;
                var newParam = args[arity].call(args[arity], param);
                args.pop(args[arity]); //删除并返回最后一个元素
                // newParam是上一个参数的运行结果,我们可以打印出来查看它的值
                args.push(newParam);
                console.log(newParam);
                return compose(...args);
            } else if (arity == 1) {
                // 将操作目标放在最后一个参数,目标可能是一个函数
                // 也可能是一个值,因此可以针对不同的情况做不同的处理
                if (!tag) {
                    return args[0].bind(null, args[1]);
                } else {
                    return args[0].call(null, args[0]);
                }
            }
        }
        window.renderIndex = compose(withLogin, withEnvironment, renderIndex);
        console.log(window.renderIndex());

柯里化函数

// 下面两个是等价
        function add(a, b, c) {
            console.log(a + b + c);
        }

        function _add(a) {
            return function(b) {
                return function(c) {
                    return a + b + c;
                }
            }
        }
        add(1, 2, 3);
        console.log(_add(1)(2)(3));

        // 利用封装的思路
        // arity  用来标记剩余参数
        // args 用来收集参数
        function createCurry(func, arity, args) {
            // 第一次执行时,并不会传入arity,而是直接获取func参数的个数func.length
            var arity = arity || func.length;
            // 第一次执行也不会传入args,而默认为空数组
            var args = args || [];

            var wrapper = function() {
                // 将wrapper中的参数收集到args中
                var _args = [].slice.call(arguments);
                [].push.apply(args, _args);
                // 如果参数个数小于最初的func.length,则递归调用,继续收集参数
                if (_args.length < arity) {
                    arity -= _args.length;
                    return createCurry(func, arity, args);
                }
                return func.apply(func, args);
            }
            return wrapper;
        }

        // 柯里化函数应用
        function check(reg, targetString) {
            return reg.test(targetString);
        }
        var _check = createCurry(check);
        // var checkPhone = _check(/^1[3456789]\d{9}$/);
        var checkEmail = _check(/^([a-zA-Z\d])(\w|\-)+@[a-zA-Z\d]+\.[a-zA-Z]{2,4}$/);
        console.log(checkEmail("iskr1024@gmail.com"));;
        // console.log(checkPhone("15179505555"));
        // 在这个过程中可以发现,柯里化能够应付更加复杂的逻辑封装。当情况变得多变是,柯里化依然能够应付自如

        // 普通思维封装map   把数组元素转换成百分数
        function getnewArr(arr) {
            return arr.map(function(item) {
                return item * 100 + '%';
            })
        }
        console.log(getnewArr([1.5, 0.3, 0.6]));
        // 利用柯里化函数封装
        function _map(fn, arr) {
            return arr.map(fn);
        }
        var _newMap = createCurry(_map);
        var getNewarr = _newMap(function(item) {
            return item * 100 + '%';
        });
        console.log(getNewarr([1, 0.8, 0.12]));
        // 过滤数组中的字符串
        function _filter(fn, arr) {
            return arr.filter(fn);
        }
        (function() {
            var _newFilter = createCurry(_filter);
            var getNewfilter = _newFilter(function(item) {
                if (typeof item == 'number') {
                    return item;
                }
            });
            console.log(getNewfilter([1, '5', 6, '7', 4])); //1,6,4
        })();
        (function() {
            // 利用筛选
            var _newFilter = createCurry(_filter);
            var find20 = _newFilter(function(item) {
                if (item === 20) {
                    return item;
                }
            })
            console.log(find20([455, 20, '20', 12, 20])); //20,20
        })();
        // 柯里化魅力无穷
        // 弊端:柯里化通用式里调用了arguments对象,使用了递归与闭包。柯里化的自由度是以牺牲了一定的性能为代价换来的。
        // 只有情况变得复杂时,才是柯里化大显身手的时候。

        // 面试题  柯里化
        // add(1)(2)(3)(4)=10;
        // add(1)(2)=3;
        // add(1,2,3)(4)=10;
        // add(1,2)(3,4,5)=10;
        (function() {
            function add() {
                // 第一次执行时,定义一个数组专门用来存储所有的参数;
                var _args = [].slice.call(arguments); //ES5写法
                // _args.push(...arguments); //add(1, 2, 3);  [1,2,3]   ES6写法
                // 在内部声明一个函数,利用闭包的特性保存_args并收集所有的参数值;
                var adder = function() {
                    var _adder = function() {
                        _args.push(...arguments);
                        return _adder;
                    };
                    // 利用隐式转换的特性,当最后执行时隐式转换,计算最终的值并返回
                    _adder.toString = function() {
                        return _args.reduce(function(a, b) { //计算数组的总和
                            return a + b;
                        });
                    };
                    return _adder;
                }

                return adder(..._args);
            }
            var n1 = add(1)(2)(3)(4)(5); //15
            // 可以利用隐式转换的特性参与计算
            console.log(n1 + 10); //25
            // 也可以继续传入参数,得到的结果再次利用隐式转换参与计算
            console.log(n1(10) + 10); //35
        })();
        (function() {
            function demo(a) {
                return function demo1(b) {
                    return a + b;
                }
            }
            console.log(demo(1)(2));
        })();

组合函数

//代码组合  回顾高阶函数的应用
        (function() {
            var env = {
                isMobile: false,
                isAndroid: false,
                isIOS: false
            }
            var ua = navigator.userAgent;
            env.isMobile = 'ontouchstart' in document;
            env.isAndroid = !!ua.match(/android/);
            env.isIOS = !!ua.match(/iphone/);
            var withEnvironment = function(basicFn) {
                return basicFn.bind(null, env);
            }
            window.withEnvironment = withEnvironment;
        })();

        function compose(...args) {
            var arity = args.length - 1;
            var tag = false;
            if (typeof args[arity] === 'function') {
                console.log(tag);
                tag = true;
            }
            if (arity > 1) {
                var param = args.pop(args[arity]);
                arity--;
                var newParam = args[arity].call(args[arity], param);
                args.pop(args[arity]); //删除并返回最后一个元素
                // newParam是上一个参数的运行结果,我们可以打印出来查看它的值
                args.push(newParam);
                return compose(...args);
            } else if (arity == 1) {
                // 将操作目标放在最后一个参数,目标可能是一个函数
                // 也可能是一个值,因此可以针对不同的情况做不同的处理
                if (!tag) {
                    return args[0].bind(null, args[1]);
                } else {
                    return args[0].call(null, args[0]);
                }
            }
        }
        var fn3 = function(a) {
            return a + 10
        };
        var fn2 = function(a) {
            return a + 20
        };
        var fn1 = function(a) {
            return a + 30
        };
        var bar = compose(fn1, fn2, fn3, 10);
        console.log(bar());//70

        // var base = function() {
        //     return arguments[0] + arguments[1];
        // }
        // var foo1 = function(fn) {
        //     return fn.call(null, 20);
        // }
        // var foo2 = function(fn) {
        //     return fn.call(null, 30);
        // }
        // var bs = compose(foo1, foo2)(base);
        // console.log(bs());

面对对象

对象定义

// 1.1对象被定义: 无序属性的集合,其属性可以包含基本值,对象或者函数
// 这里person就是一个对象
var person = {
    name: 'iskr',
    age: 16,
    // value为函数
    getName: function () {
        return this.name
    },
    // value 为对象
    parent: {}
};

创建对象

// 1.2创建对象
// 可以通过关键字new来创建一个对象。
var obj = new Object();
// 也可以通过字面量的形式创建对象。
var obj = {};
// 当我们想给创建对象添加属性和方法时
obj.name = "iskr";
obj.getName = function () {
    return this.name;
};
// 也可以这样
var obj = {
    name: "iskr",
    age: 20,
    getName: function () {
        return this.name;
    }
};
// 访问属性的方式
console.log(obj.name);
console.log(obj['name']);
var _name = 'name';
console.log(obj[_name]);
['name', 'age'].forEach(function (item) {
    console.log(obj[item]);
});

构造函数与原型

// 1.3构造函数与原型
// 封装函数其实就是封装一些公共的逻辑与功能,通过传入参数的形式达到自定义的效果。当面对具有共同特征的一类事物是,就可以结合构造函数与原型的方式将这类事物封装成对象。
// 构造函数
var Person = function (name, age) {
    this.name = name;
    this.age = age;
};
Person.prototype.getName = function () {
    return this.name;
};
// 具体某一个人特定的属性,通常放在构造函数中。
// 所有人公共的方法与属性,通常会放在原型对象中。prototype

var p1 = new Person('iskr', 19);
var p2 = new Person('tom', 22);
console.log(p1.getName());
// 温习一下new关键字具体帮我们做了什么事情
// 把函数以参数的形式传入
function New(fn) {
    // 声明一个中间对象,该对象为最终返回的实例
    var res = {};
    if (fn.prototype !== null) {
        // 将实例的原型指向构造函数的原型
        res.__proto__ = fn.prototype;
    }
    // ret为构造函数的执行结果,这里是通过apply,
    // 将构造函数内部的this指向修改为res,即实例对象
    var ret = fn.apply(res, Array.prototype.slice.call(arguments, 1));
    // 当我们在构造函数中明确指定了返回对象
    if ((typeof ret === 'object' || typeof ret === 'function') && ret !== null) {
        return ret;
    }
    // 如果没有明确指定返回对象,则默认返回res,这个res就是实例对象
    return res;
}
// 1.先创建一个新的,空的实例对象。
// 2.将实例对象的原型,指向构造函数的原型
// 3.将构造函数内部的this,修改为指向实例
// 4.最后返回该实例对象
var p3 = New(Person, 'Iskr', 13);
console.log(p3.getName());

公有方法和私有方法

// 公有方法和私有方法
// 在原型中声明的方法和属性我们称为公有属性与方法
// 而在构造函数中声明一个方法是,每创建一个实例,该方法都会被重新创建一次。而在原型中的方法仅仅只会创建一次
// 这也是我们称其为私有方法的原型之一。
function Per(name) {
    this.name = name;
    this.getName = function () { //私有方法
        return this.name + '你访问了私有方法'; //优先访问构造函数的方法、属性
    }
}
Per.prototype.getName = function () {
    return this.name + '你访问了公有方法!';
}
var per1 = new Per('iskr');
console.log(per1.getName());

// 可以通过in来判断对象是否拥有某一个方法、属性。无论是公有私有
console.log('getName' in per1); //true
console.log('age' in per1); //false
// 还可以检测所处的环境是否在移动端
//var isMoblie = 'ontouchstart' in document; //因为只有移动端环境才有ontouchstart

原型的简写方法

// 1.4更简单的原型写法
function Person_1() { }
Person_1.prototype.getName = function () { }
Person_1.prototype = {
    constructor: Person_1,
    getName: function () { }
};
// 以上就是三种写法喽

原型链

// 1.5原型链
// 原型对象其实就是普通对象。几乎所有的对象都可以是原型对象,也可以是实例对象,还可以是构造函数,甚至可以身兼数职
// 判断一个对象是否为一个对象的构造函数实例,可以使用instanceof关键词
function Foo() { }
var foo = new Foo();
var istt = foo instanceof Foo;
console.log(istt);
// 所有函数与对象都有有一个toString与valueOf方法,
// 就是来源于Object.prototype
console.log(Object.prototype);

var add = new Function("a", "b", "return a+b");
// 等价于
var add = function (a, b) {
    return a + b;
};
// 等价的原理就是
add.__proto__ === Function.prototype; //true
console.log(add.__proto__ === Function.prototype); //true
console.log(Function.prototype.constructor === Function); //true
console.log(Function.__proto__ === Function.prototype); //true
console.log(add instanceof Function); //true

实例方法、原型方法、静态方法

// 1.6 实例方法、原型方法、静态方法

function Foo() {
    this.bar = function () {
        return 'bar in Foo'; //实例方法
    }
}
Foo.bar = function () {
    return 'bar in static'; //静态方法  直接挂载在构造函数上
};
Foo.prototype.bar = function () {
    return 'bar in prototype'; //原型方法
};

继承

// 1.7继承
(function () {
    var Person = function (name, age) {
        this.name = name;
        this.age = age;
    }
    Person.prototype.getName = function () {
        return this.name;
    };
    // 构造函数的继承比较简单,可以借助call/apply来实现。
    var Stu = function (name, age, gnv) {
        // 通过call方法还原Person 构造函数中的所有处理逻辑
        Person.call(Stu, name, age);
        this.gnv = gnv;
    };
    // 等价于
    var Stu = function (name, age, gnv) {
        this.name = name;
        this.age = age;
        this.gnv = gnv;
    };
    // 其实只需要让子类对象的原型成为父类对象的一个实例,然后通过__proto__访问父类的原型,这样就继承了父类原型中的方法属性
    function create(proto, options) {
        // 创建一个空对象
        var tmp = {};
        // 让这个新的空对象成为父类的实例
        tmp.__proto__ = proto;
        // 传入的方法都挂载到新的对象上,新的对象将作为子类对象的原型
        Object.defineProperties(tmp, options);
        //defineProperties: 方法直接在一个对象上定义新的属性或修改现有属性,并返回该对象。
        return tmp;
    }

    Stu.prototype = create(Person.prototype, {
        // 不要忘记了重新指定构造函数
        constructor: {
            value: Stu
        },
        getGrade: {
            value: function () {
                return this.gnv;
            }
        }
    });
    var s1 = new Stu('iskr', 18, '女');
    console.log(s1.getName());
    console.log(s1.getGrade());
    console.log(s1.age);
})();

属性类型

// 1.8属性类型
// 1.configurable:表示该属性是否能被delete删除,其值为false是,其他特性特性也不能被改变。默认值为true
// 2.enumerable:是否能枚举。级是否能被for-in遍历。默认是true
// 3.writable:是否能被修改值,默认为true
// 4.value:该属性的具体值时多少,默认为undefined
// 5.get:当通过Person.name访问name的值时,set方法将被调用。该方法可以自定义设置值的具体方式。set的默认值为undefined
// 6.set:当通过Person.name='iskr'设置name的值时,set方法将被调用。该方法可以自定义设置值的具体方式。set默认值为undefined
// 注意:需要注意的是不能同时设置value、writable与get、set的值。
// Object.defineProperty只能设置一个属性的属性特性
// Object.defineProperties可以同时设置多个属性的特性

// get/set
(function () {
    var Person = {};
    Object.defineProperty(Person, 'name', {
        get: function () {
            return 'iskr';
        },
        set: function (name) {
            console.log(name + ' in set');
        }
    });
    console.log(Person.name);
    Person.name="hah";
    console.log(Person.name);
    // 注意:尽量同时设置get和set,如果、只设置了get,那么将无法设置该属性值,如果只设置了set,那么将无法读取该属性的属性值。
})();
(function(){
    var Person={};
    Object.defineProperties(Person,{
        name:{
            value:'iskr',
            configurable:true
        },
        age:{
            get:()=>{
                return this.value || 19;
            },
            set:(value)=>{
                this.value=value;
            }
        }
    })
    console.log(Person.name);
    console.log(Person.age);
    Person.age=18;
    console.log(Person.age);
    var dqu=Object.getOwnPropertyDescriptor(Person,'name');//读取属性的特性值
    console.log(dqu);
})();

JQuery封装

(function (ROOT) {
    // 构造函数
    var jQuery = function (selector) {
        // 在该方法中直接返回new创建的实例,
        // 因此这里的init才是真正的构造函数
        // 3.在构造函数里面返回了init实例对象
        return new jQuery.fn.init(selector);
    };
    jQuery.fn = jQuery.prototype = {
        constructor: jQuery,
        // 1.在原型上添加了init方法
        init: function (selector) {
            var elm, selector;
            elm = document.querySelector(selector);
            this[0] = elm;

            // 在Jquery中返回的是一个由所有原型属性方法组成的数组,
            // 在这里做了简化,直接返回this既可
            return this;
        },
        // 在原型上添加一堆方法
        toArray: function () { },
        get: function () { },
        set: function () { }
    }
    // 2.之后又将init的原型指向了jquery.prototype
    jQuery.fn.init.prototype = jQuery.fn;

    // 实现jquery的两种扩展方法
    jQuery.extend = jQuery.fn.extend = function (options) {
        // 在Jquery源码中根据参数不同进行不同的判断,这里假设只有一种方式
        var target = this;
        var copy;
        for (name in options) {
            copy = options[name];
            target[name] = copy;
        }
        return target;
    };
    // jquery利用上面实现的扩展机制,添加了许多方法

    // 添加静态方法
    jQuery.extend({
        ajax:function(){console.log('Ajax')},
        type:function(){},
        isFunction:function(){}
    });
    jQuery.fn.extend({
        query:function(){},
        attr:function(){},
        prop:function(){},
        val:function(){},
    });

    // 4.最后暴露接口将$与方法jquery对等起来
    ROOT.jQuery = ROOT.$ = jQuery;
})(window);

ES6与模块化

常用语法知识

  1. 声明一个引用可以改变的变量时用let
  2. 声明一个引用不能被改变的变量时使用const
	{
            let a = 12;
        }
        // console.log(a);//a is not defined
        // console.log(b);//undefined
        var b = 20;

        const arr = [1, 2, 3, 5, 6, 7];
        // arr=4; Uncaught TypeError: Assignment to constant variable.
        arr.push(10);//当声明一个引用数据类型时,也会使用const。尽管可能改变该数据的值,但是必须保持的它引用不变
        arr.forEach(function (item) {
            const temp = item + 1;
            // console.log(temp);
        })

箭头函数

  1. 与function相比,箭头函数是用起来更加舒服的一种用法

     		// ES5
            var fn1 = function (a, b) {
                return a + b;
            }
            // ES6
            var fn2 = (a, b) => a + b;
            // ES5
            var foo1 = function () {
                var a = 10;
                var b = 20;
                return a + b;
            }
            // ES6
            const foo2 = () => {
                const a = 10;
                const b = 20;
                return a + b;
            }
    
  2. 函数内部this指向

    	//普通写法
    	var name = 'iskr';
            var getName = function () {
                console.log(this.name);
            }
            var person = {
                name: 'person',
                getName: getName
            }
            var other = {
                name: 'other'
            }
    
            getName();//独立调用,this指向undefined,并自动转换成window
            person.getName();//被person调用,this指向person
            getName.call(other);//call修改了this指向,指向other
    
    	//ES6
    	var name = 'iskr';
            var getName = () => {
                console.log(this.name);
            }
            var person = {
                name: 'person',
                getName: getName
            }
            var other = {
                name: 'other'
            }
            // 箭头函数中的this,就是声明函数是所处上下文中的this,它不会被其他方式所改变。
            getName();//iskr
            person.getName();//iskr
            getName.call(other);//iskr	
    
    	// 在实践中,常常会遇到this在传递过程中发生改变,因此带来的困扰
            var Mto = function (name) {
                this.name = name;
            }
            Mto.prototype = {
                coustructoe: Mto,
                do: function () {
                    console.log(this.name);
                    document.onclick = function () {
                        console.log(this.name);
                    }
                }
            }
            var m = new Mto('名字');
            m.do();//undefined
            //用箭头函数就能解决此问题
            var Mto = function (name) {
                this.name = name;
            }
            Mto.prototype = {
                coustructoe: Mto,
                do: function () {
                    console.log(this.name);
                    document.onclick = () => {
                        console.log(this.name);
                    }
                }
            }
            var m = new Mto('名字');
            m.do();//名字
    
    	// 除此 arguments还有一个需要注意的地方,箭头含糊中没有arguments对象
            var add = function (a, b) {
                // console.log(arguments);
                return a + b;
            }
            add(1, 2);
            // 0: 1
            // 1: 2
            // callee: ƒ (a,b)
            // length: 2
            // Symbol(Symbol.iterator): ƒ values()
    
            var add = (a, b) => {
                // console.log(arguments);
                return a + b;
            }
            add(1, 2);//Uncaught ReferenceError: arguments is not defined
    

模本字符串

// 模板字符串
        // 模板字符串为了解决传统字符串拼接不便利而出现的
        var a = 1;
        var b = 2
        var str = a + '+' + b + '=' + (a + b);
        console.log(str);//1+2=3
        // ES6
        var str = `${a}+${b}=${a + b}`;
        console.log(str);//1+2=3

        var html = `
        <div>
        一个盒子
        <p>${a}</p>
        <span>${b}</span>
        </div>`;
        console.log(html);
        // 输出
        // <div>
        // 一个盒子
        // <p>1</p>
        // <span>2</span>
        // </div>

解构结构

  1. 解构结构是一种从对象或者数组中取得值的一种全新的写法方式。

     var tom = {
                name: 'isll',
                age: 18,
                job: 'JavaScript'
            }
            // 传统获取值
            var name = tom.name;
            var age = tom.age;
            console.log(name);//isll
            // ES6
            var { name, age } = tom;
            console.log(name);//iskll
    
  2. 给变量指定默认值

    	// 如果数据中能找到name,则变量的值与数据中相等;若找不到,则使用默认值
            var { name = "iskr", status = "厉害" } = tom;
            console.log(name, status);//isll,厉害
            // obj1将job键名替换掉了,job失效
            var { job: obj1 } = tom;
            console.log(obj1, name);
            const pelples = {
                counts: 100,
                detail: {
                    iskr: {
                        name: 'iskr',
                        age: 18,
                        job: 'recat'
                    }
                }
            }
    
  3. 获取iskr对象

    	const { detail: { iskr } } = pelples;
            console.log(iskr);//{name: "iskr", age: 18, job: "recat"}
            // 直接获取job
            const { detail: { iskr:{job} } } = pelples;
            console.log(job);//recat
    
  4. 数组的解析结构

    // 数组的解析结构
            var arrNum=[1,2,3];
            var [a,b,c]=arrNum;
            console.log(a,b,c);
            var [a,b]=arrNum;
            console.log(a,b);
    // 与对象不同的是,数组中变量和值的关系与序号是一一对应的,这是一个有序的对应关系。而对象则根据属性名一一对应,这是一个无序的对应关系。
    
    

展开运算符

  1. 在ES6中,使用…来表示展开运算符,它可以展开数组对象

  2. 解析数组

    // 首先声明一个数组
            const arr1 = [1, 2, 3];
            // 其声明另外一个数组,我们期望新数组中包含数组arr1的所有元素
            // 因此可以用展开运算符...
            const arr2 = [...arr1, 4, 5, 6];
            console.log(arr2);//[1, 2, 3, 4, 5, 6]
    
  3. 解析对象

    // 当然对象也可
            const obj1 = {
                a: 1,
                b: 2,
                c: 3
            };
            const obj2 = {
                ...obj1,
                d: 4,
                e: 5
            }
            console.log(obj2);//{a: 1, b: 2, c: 3, d: 4, e: 5}
    
  4. 运用到函数参数中

    // 在解析结构中,也常常使用展开运算符
            const iskr = {
                name: 'iskr',
                age: 18,
                job: 'JavaScript'
            }
            const { name, ...other } = iskr;
            console.log(other);//{age: 18, job: "JavaScript"}
    
            // 求所有参数之和
            const add = (a, b, ...other) => {
                return other.reduce((m, n) => m + n) + a + b;
            }
            console.log(add(1,2,3,4,5,6,7,8,9));//45
    

Promise详解

  1. 异步与同步

    同步:是指当发起一个请求时,如果没得到请求结果,代码将会继续等待,直到得到结果,才会执行后面的代码。

    异步:是指当发起一个请求时,不会等待请求结果,而是直接继续执行后面的代码。

    模拟同步:使用async/await语法来模拟同步的效果

    function fn() {
                return new Promise(function (res, rej) {
                    setTimeout(function () {
                        res("成功");
                    }, 1000)
                })
            }
    
            // 在该函数的基础上,可以使用async/await语法来模拟同步的效果
    
            var foo1 = async function () {
                var f = await fn();//模拟了同步执行的结果
                console.log(f);//成功
                console.log('nextCode');
            }
            foo1();
            // 输出 一秒之后全部输出
            // 成功
            // nextCode
    
            // 异步效果代码会有不同的输出结果
            var foo2 = function () {
                var f = fn();
                f.then((res) => {
                    console.log(res);//成功
                });
                console.log();
                console.log('nextCode');
            }
            foo2();
            // 输出
            // nextCode 停顿一秒后在输出成功
            // 成功
    
  2. Promise

    Ajax:是网页与服务器进行数据交互的一种技术、我们可以通过服务端提供的接口,用Ajax向服务端请求我们需要的数据。

    // 简单的Ajax原生实现
            var xhr = new XMLHttpRequest();
            xhr.open('GET', 'http://jsonplaceholder.typicode.com/posts');
            xhr.send();
            xhr.onreadystatechange = function () {
                if (xhr.readyState == 4) {
                    var res = xhr.responseText;
                    // console.log(res);
                }
            }
    

    JSONP请求的一种解决方案

    	ajax({
                jsonp: 'JSONP',
                url: 'https://j.i8tq.com/weather2020/search/city.js',
                data: {},
                time: 1000,
                success: function (data) {
                    console.log(data)
                },
                error: function (error) {
                    console.log(error)
                }
            })
    //新建一个js文件放进去   
    const ajax = (params = {}) => {
        const nowJson = params.jsonp ? jsonp(params) : json(params);
        function jsonp(params) {
            //创建script标签并加入到页面中
            var callbackName = params.jsonp;
            var head = document.getElementsByTagName('head')[0];
            // 设置传递给后台的回调参数名
            params.data['callback'] = callbackName;
            var data = formatParams(params.data);
            var script = document.createElement('script');
            head.appendChild(script);
    
            window[callbackName] = function (jsonData) {
                head.removeChild(script);
                clearTimeout(script.timer);
                window[callbackName] = null;
                params.success && params.success(jsonData);
            };
            //console.log(window[callbackName])
            //console.log(params.url + '?' + data)
    
            //url形式传参
            //说明:下面的script加载资源后会返回一个自执行函数:[callbackName](responseData),这个形式就是去执行一个函数,函数的名字是一个参数
            //      同时在windows对象上定义了这个函数:[callbackName] = function(responseData){},这时就会调用这个函数
            script.src = params.url + '?' + data;
    
            //为了得知此次请求是否成功,设置超时处理
            if (params.time) {
                script.timer = setTimeout(function () {
                    window[callbackName] = null;
                    head.removeChild(script);
                    params.error && params.error({
                        message: '超时'
                    });
                }, params.time);
            }
        }
        //格式化参数
        function formatParams(data) {
            var arr = [];
            for (var name in data) {
                arr.push(encodeURIComponent(name) + '=' + encodeURIComponent(data[name]));
            };
    
            // 添加一个随机数,防止缓存
            arr.push('v=' + random());
            return arr.join('&');
        }
    
        // 获取随机数
        function random() {
            return Math.floor(Math.random() * 10000 + 500);
        }
        function json(params) {
            // 请求方式,默认是GET
            params.type = (params.type || 'GET').toUpperCase();
            // 避免有特殊字符,必须格式化传输数据
            params.data = formatParams(params.data);
            var xhr = null;
    
            // 实例化XMLHttpRequest对象
            if (window.XMLHttpRequest) {
                xhr = new XMLHttpRequest();
            } else {
                // IE6及其以下版本
                xhr = new ActiveXObjcet('Microsoft.XMLHTTP');
            };
    
            // 监听事件,只要 readyState 的值变化,就会调用 readystatechange 事件
            xhr.onreadystatechange = function () {
                // readyState属性表示请求/响应过程的当前活动阶段,4为完成,已经接收到全部响应数据
                if (xhr.readyState == 4) {
                    var status = xhr.status;
                    // status:响应的HTTP状态码,以2开头的都是成功
                    if (status >= 200 && status < 300) {
                        var response = '';
                        // 判断接受数据的内容类型
                        var type = xhr.getResponseHeader('Content-type');
                        if (type.indexOf('xml') !== -1 && xhr.responseXML) {
                            response = xhr.responseXML; //Document对象响应
                        } else if (type === 'application/json') {
                            response = JSON.parse(xhr.responseText); //JSON响应
                        } else {
                            response = xhr.responseText; //字符串响应
                        };
                        // 成功回调函数
                        params.success && params.success(response);
                    } else {
                        params.error && params.error(status);
                    }
                };
            };
    
            // 连接和传输数据
            if (params.type == 'GET') {
                // 三个参数:请求方式、请求地址(get方式时,传输数据是加在地址后的)、是否异步请求(同步请求的情况极少);
                xhr.open(params.type, params.url + '?' + params.data, true);
                xhr.send(null);
            } else {
                xhr.open(params.type, params.url, true);
                //必须,设置提交时的内容类型
                xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
                // 传输数据
                xhr.send(params.data);
            }
        }
    }
    //全局导出   
    window.ajax=ajax;
    
    

    ​ 在Ajax的原生实现中,利用onreadystatechange事件,只有当该事件触发并且符合一定条件是,才能拿到我们想要的数据,之后才能开始处理数据。

    ​ 这样做看上去并没有什么,但如果这个时候,还需要做另外一个Ajax请求,那么这个新的Ajax请求中的一个参数,则必须从上一个Ajax请求中获取,这个时候我们就不得不这样做了。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wTzPx7p1-1604546278790)(C:\Users\19752\AppData\Roaming\Typora\typora-user-images\image-20201103131641299.png)]

    ​ 当第三个Ajax(甚至更多)仍然依赖上一个请求的时候,此时的代码就像变成了一场灾难。我们在需要不停的嵌套回调函数,以确保下一个接口所需要的参数正确性。这样的灾难,我们称之为回调地狱

    ​ 为了解决回调地狱,为了使代码更具有可以读性和可维护性,我们需要将数据请求与数据处理区分开来。

    当我们想要确保某些代码在某某之后执行时

    可以利用函数调用栈,将想要执行的代码放入回调函数中。

    // 一个简单的封装
            function want() {
                console.log("这是我们想要执行的代码");
            }
            function fn(want) {
                console.log("这里表示执行了一大堆各种代码");
                // 其他代码执行完毕后,最后在执行回调函数
                want && want();
            }
            fn(want);
    

    利用队列机制来确保我们想要执行的代码压后执行。

    	function want() {
                console.log("这是我们想要执行的代码");
            }
            function fn(want) {
                // 将想要执行的代码放在队列中后,根据事件循环机制
                // 就不用将它放在最后面了,可以自由选择
                want && setTimeout(want, 0);//异步代码在后面执行
                console.log("这里表示执行了一大堆各种代码");
            }
            fn(want);
    

    与setTimeout类似,Promise也可以认为是一种任务分发器。它将任务分配到Promise队列中,通常的流程是首先先发起一个请求,然后等待请求,然后等待(等待时间无法确定)并处理请求结果。

    	var tag = false;
            var p = new Promise(function (resolve, reject) {
                if (tag) {
                    resolve("this is true");
                } else {
                    reject("this is false");
                }
            });
            p.then((res) => {//true
                console.log(res);
            }).catch((err) => {//fasle
                console.log(err);
            })
    

    Promise相关知识:

    1. new Promise表示创建一个Prosime实例对象。

    2. Promise函数中的第一个参数为回调函数,也称之为executor。通常情况下,在这个函数中,会执行发起请求操作,并修改结果的状态值。

    3. 请求结果有三种状态,分别是pending(等待中,表示还没有得到结果)、resolved(得到了我们想要的结果,可以继续执行)、以及rejected(得到错误的,或者不是我们期望的结果,拒绝继续执行)。请求结果的默认状态为pending。在executor函数中,可以分别使用resolved与reject将状态修改为对应的resolved与rejected。resolve、reject是executo函数的两个参数,它能够将请求结果的具体数据传递出去。

    4. Promise使用拥有then方法,可以用来处理当请求结果状态变成resolved时的逻辑。then的第一个参数为回调函数,该函数的参数是resolve传递出来的数据。

    5. Promise使用拥有catch方法,可以用来处理当请求结果状态变成rejected时的逻辑。catch的第一个参数为回调函数,该函数的参数是reject传递出来的数据。

    6. 在结合案例看看把~

      function fn(num) {
                  return new Promise(function (resolve, reject) {
                      if (typeof num == 'number') {
                          resolve("是一个数字");
                      } else {
                          reject("不是一个数字");
                      }
                  })
              }
              fn(21).then((res) => {//是一个数字
                  console.log(res);
              }).catch((err) => {
                  console.log(err);
              })
              console.log("next code");//next code 先执行
      

      ​ 可以简写

      	fn("a").then(null, (err) => {
                  console.log(err);
              });
      

      then方法因为返回的仍是一个Promise实例对象,因此then方法可以嵌套使用。

      	function fn(num) {
                  return new Promise(function (resolve, reject) {
                      setTimeout(function () {
                          if (typeof num == 'number') {
                              resolve(num);
                          } else {
                              reject(num + "不是一个数字");
                          }
                      }, 2000);
                  })
              }
              fn(6).then((res) => {
                  console.log(res);//6
                  return res + 1;
              }).then((res) => {
                  console.log(res);//7
                  return res + 1;
              }).then((res) => {
                  console.log(res);//8
                  return res + 1;
              }).then((res) => {
                  console.log(res);//9
                  return res + 1;
              }).then((res) => {
                  console.log(res);//10
                  return res + 1;
              })
              console.log("next code");//next code 先执行
      

      封装一个原生Ajax请求

      	function getJSON(url) {
                  return new Promise(function (resolve, reject) {
                      var xhr = new XMLHttpRequest();
                      xhr.open('get', url, true);
                      xhr.send();
                      xhr.onreadystatechange = function () {
                          if (xhr.readyState == 4) {
                              if (xhr.status == 200) {
                                  try {
                                      var res = JSON.parse(xhr.responseText);
                                      // 得到正确的结果修改状态并将数据传递出去
                                      resolve(res);
                                  } catch (error) {
                                      reject(error);
                                  }
                              } else {
                                  // 得到错误结果并抛出异常
                                  reject(new Error(xhr.statusText));
                              }
                          }
                      }
                  });
              }
              // 封装好之后,使用就很简单了
              getJSON('http://jsonplaceholder.typicode.com/posts').then(res => {
            		//处理成功的结果      
                  console.log(res);
              }).cacth(err=>{
                  console.log(err);
              })
      

      Promise.all

      ​ 当有一个Ajax请求,它的参数需要另外两个或者甚至更多个请求都有返回结果之后才能确定是,就需要用到Promise.all来帮助我们应对这个场景。

      ​ Promise.all接收一个Promise对象组成的数组作为参数,当这个数组中所有的Promise对象状态都变成resolved或者rejected时,它才会去调用then方法。

      	var ulr1 = 'http://jsonplaceholder.typicode.com/posts';
              // var url2 = '';
              var url2 = 'http://jsonplaceholder.typicode.com/posts';
              function rendAll() {
                  return Promise.all([getJSON(ulr1), getJSON(url2)]);
              }
              rendAll().then(res => {
                  console.log(res);
              })
      

      Promise.race

      ​ 与Promise.all相似的是。Promise.race也是以一个Promise对象组成的数组作为参数,不同的是,只有数组当中一个Promise状态变成resolved或者rejected时就调用then方法

      	// var url1 = '';   
              var url2 = '';
              var url1 = 'http://jsonplaceholder.typicode.com/posts';
              // var url2 = 'http://jsonplaceholder.typicode.com/posts';
              function rendRace() {
                  return Promise.race([getJSON(url1), getJSON(url2)]);
              }
              rendRace().then(res => {
                  console.log(res);
              }).catch(err => {
                  console.log(err);
                  //SyntaxError: Unexpected token < in JSON at position 0
                  //at JSON.parse (<anonymous>)
                  //at XMLHttpRequest.xhr.onreadystatechange (ES6-4.html:108)
            		//输出结果有点与的意思&&遇假则假;      
              })
      

      async/await

      1. 异步问题不仅可以使用前面学到的Promise来解决。还可以用async/await来解决。

      2. async/await是ES7中新增的语法,虽然现在最新的Chrome浏览器已经支持该语法,但在实际使用中,仍需要构建工具配合。

      3. async的具体使用

        	async function foo(){
                    return 30;
                }
                console.log(foo());
                const fn=async ()=>{
                    return 30;
                }
                console.log(fn());
                // Promise {<fulfilled>: 30}
                // __proto__: Promise
                // [[PromiseState]]: "fulfilled"
                // [[PromiseResult]]: 30
        

        总结上边:可以发现fn函数运行后返回的是一个标准的Promise对象,因此可以猜想async其实是Promise的一个语法糖。

        	fn().then(res=>{
                    console.log(res);//30
                })
        

        await的含义就是等待,意思就是让代码等待await后面的函数运行完并且有了结果之后,才会继续执行下面的代码。这这正是同步的效果。

        	function fn(){
                    return new Promise(function(resolve,reject){
                        setTimeout(()=>{
                            resolve(30);
                        },2000);
                    });
                }
                const foo=async ()=>{
                    const t=await fn();
                    console.log(t);
                    console.log('next code');
                    // 30
                    // next code
                }
                foo();
        

        通过运行这个例子可以看出,在async函数中,当运行遇到await时,就会等待awati后面的函数运行完毕,而不会直接执行next code。

        如果直接async使用then方法,很显然使用async/await,代码结构会更简洁,逻辑也更清晰

        	const foo1 = () => {
                    return fn().then(res => {
                        console.log(res);
                        console.log('next code');
                    })
                }
                foo1();	
        
        	//差点就搞混了
        	function demo() {
                    setTimeout(() => {
                        console.log(3000);
                    }, 3000);
                    console.log('next');
                    // next
                    //3000
                }
                demo();
        

        异常处理

        在Promise中,是通过catch的方式捕获异常的,当使用async/await时,则是通过try/catch来捕获异常。

        function fn() {
                    return new Promise(function (resolve, reject) {
                        setTimeout(() => {
                            reject('new error');
                        }, 1000)
                    })
                }
                var foo = async () => {
                    try {
                        await fn()
                    } catch (error) {
                        console.log(error);
                    }
                }
                foo();
        

        如果有多个await函数,那么只返回第一个捕获到的异常

        	function fn1() {
                    return new Promise(function (resolve, reject) {
                        setTimeout(() => {
                            reject('new error1');
                        }, 1000)
                    })
                }
                function fn2() {
                    return new Promise(function (resolve, reject) {
                        setTimeout(() => {
                            reject('new error2');
                        }, 1000)
                    })
                }
                var foo = async () => {
                    try {
                        await fn1();
                        await fn2();
                    } catch (error) {
                        console.log(error);//new error1只有一个结果
                    }
                }
                foo();
        

        事件循环机制

        1. 事件循环机制(Event Loop)是全面了解JavaScript代码执行顺序绕不开的一个重要知识点。虽然很多人知道这个知识点非常重要,但实际其实很少人能够真正理解它。特别是在ES6正式支持Promise之后,对于新标准中的时间循环的理解就变得更加重要。

        2. 执行上下文(Execution context)

        3. 函数调用栈(call stack)

        4. 队列数据结构(queue)

        5. Promise

          setTimeout(()=>{
                      console.log(1);
                  },0);
                  console.log(2);
                  for(var i=0;i<5;i++){
                      console.log('3-'+i);
                  }
                  console.log(4);
          
                  console.log(1);
                  for(var i=0;i<5;i++){
                      setTimeout(function(){
                          console.log('2-'+i);//2-5
                      },0);
                  }
                  console.log(3);
          

          setTimeout不是调用函数栈能解释清楚的。还是看队列

          [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-omTnpffK-1604546278791)(C:\Users\19752\Desktop\u=4007848708,1685442224&fm=15&gp=0.png)] Javascript的一个特点就是单线程,但是很多时候我们仍然需要在不同的时间去执行不同的任务,例如给元素添加点击事件,设置一个定时器,或者发起Ajax请求。因此需要一个异步机制来达到这样的目的,事件循环机制也因此而来。

          ​ 每个JavaScript程序都拥有唯一一个事件循环,大多数代码的执行顺序是可以根据函数调用栈的规则来执行,而setTimeout/setInterval或者不同的事件绑定(click、mousedown等)中的代码,则是通过队列来执行的。

           setTimeout(function () {
                      console.log('time1');//5
                  });
                  new Promise(function (resolve, reject) {
                      console.log('pro2');//1
                      resolve('pro3')//3
                  }).then(res => {
                      console.log(res);
                      console.log('pro4');//4
                  });
                  console.log('全局');//2
                  // pro2
                  //全局
                  //pro3
                  //pro4
                  //time1
          

Class和对象

语法简化写法

	const name = 'iskr';
        const age = 18;

        // ES6
        const person = {
            name,
            age
        }
        console.log(person.name);//iskr
        // ES5
        var person = {
            name: name,
            age: age
        };
        console.log(person.age);//18

对象中的方法简写

const name = 'iskr';
        const age = 18;
        const per1 = {
            name,
            age,
            getName() {//只要没有使用箭头函数,this就是还是原来的this
                return this.name;
            }
        }
        var n = per1.getName();
        console.log(n);
        // ES5
        const per2 = {
            name: name,
            age: age,
            getName: function () {//只要没有使用箭头函数,this就是还是原来的this
                return this.name;
            }
        }
        var n = per1.getName();
        console.log(n);

可以使用变量作为对象的属性 ,只需要用中括号包裹起来[]

const name = 'iskr';
        const age = 18;
        const per={
            [name]:name,
            [age]:age
        };
        console.log(per[18]);//18

class

	// ES5
        // 构造函数
        function Person(name, age) {
            this.name = name;
            this.age = age;
        }
        // 原型方法:不会创建一次调用一次
        Person.prototype.getName = function () {
            return this.name;
        }
        // ES6
        class Person{
            constructor(name,age){//构造方法
                this.name=name;
                this.age=age;
            }
            getName(){//原型方法
                return this.name;
            }
        }
		var p=new Person('iskr',18);
 		console.log(p.name);//iskr

实际中不同的写法

// ES6
        class Person{
            constructor(name,age){//构造方法
                this.name=name;
                this.age=age;
            }
            getName(){//原型方法
                return this.name;
            }
            static a=10;//等同于 Person.a=10
            c=20;//表示在构造函数中添加属性,在构造函数中等同于this.c=20

            // 箭头函数的写法表示在构造函数中添加的方法,在构造函数中等同于this
            // getAge=function(){}
            getAge=()=>this.age;
        }
        var p=new Person('iskr',18);
        console.log(p.name);//iskr
        console.log(p.getAge());//18
        console.log(Person.a);//10
        console.log(p.c);//20

更多写法

	// ES6
        class Person {
            constructor(name, age, x) {//构造方法
                this.name = name;
                this.age = age;
                let _x = x;//这才是私有变量
                this.showX = function () {
                    return _x;
                }
            }
            getName() {//原型方法
                return this.name;
            }
            static a = 10;//等同于 Person.a=10
            c = 20;//表示在构造函数中添加属性,在构造函数中等同于this.c=20

            // 箭头函数的写法表示在构造函数中添加的方法,在构造函数中等同于this
            // getAge=function(){}
            getAge = () => this.age;
        }
        var p = new Person('iskr', 18,2);
        console.log(p.name);//iskr
        console.log(p.getAge());//18
        console.log(Person.a);//10
        console.log(p.c);//20
        console.log(p.showX());//2
        console.log(p._x);//undefined访问不到

继承

	//继承了上面的类 	
	class Student extends Person{
            constructor(name,age,job){
                super(name,age);
                this.job=job;
            }
            getJob(){
                return this.job;
            }
        }
        var s=new Student('张三',19,'职业:程序员')
        console.log(s.getJob());

区别ES5中利用call/apply继承构造函数而ES6使用super

练习:

class Person {
            constructor(name, age, job) {
                this.name = name;//公有变量
                this.age = age;
                let _job = job;
                this.showJob = () => {//私有变量访问方式
                    return _job;
                }
            }
            getName() {
                return this.name;//原型方法
            }
        }
        class Student extends Person {
            constructor(name, age, job, hoby) {
                super(name, age, job);
                this.hoby = hoby;
            }
            getHoby() {
                return this.hoby;
            }
        }
        var s = new Student('iskr', 18, '程序员', '编程');

        console.log(`我的名字叫${s.name}年龄:${s.age}职业:${s.showJob()}爱好:${s.hoby}`);

模块化

我是在Vue里面测试,大家可以自行百度装下环境。

基础语法

  • import-from

    1. 首先需要知道的一个常识是,每一个文件都是一个单独的模块。

    2. 通过import指令,可以在当前模块引入去其他的模块

      import React from ‘./react’

      import 表示引入、加载一个模块

      React 可以理解成这个模块的名字。它是React模块创建时对外提供的所有接口组合成的对象。因此在代码中可以使用它来访问React模块提供的接口。例如React.Component。

      正因为此处是一个对象。因此可以使用解析结构的形式来直接获取某个特定的接口。

      import React, { Component } from ‘./react’

      from 表示模块来自于哪里。

      此处的react表示模块的出处。通常情况下应该使用路径的形式表示,例如下面的./index.css,这样表示是因为构建工具能够自动识别安装在node_modules中的模块,而不用具体显示真实路径。此处可以理解成简写

      import App from ‘./App.js’

      // 通常会省略

      import App from ‘./App’

    • export

      一个模块需要有向外提供接口的能力,在之前的例子中我们还没有学会模块化语法时。简单粗暴地将对外的接口放在window对象上,而在ES6的语法中,则是借助export命令来提供对外接口。

      export const name1=“iskr”;

      export const name2=“Javascript”;

      当在其他模块(index.js)引入该模块时,如果仅仅只是引入其中的某一个接口,那么可以这样做:

      import {name1} from ‘./module01’

      // 引入所有接口的方法

      import {name1,name2}from ‘./module01’;

      import * as module from ‘./module01’;

      还可以通过export default 来对外提供接口,这种情况下,对外接口通常是一个对象

      const name1 = "iskr";
      
      const name2 = "Javascript";
      
      
      export default {
      
        name1,
      
        name2
      
      }
      
      //针对上面引入代码
      
      import module01 from './module01'
      

      例子

      //module02
      class Person {
          constructor(name, age) {
              this.name = name;
              this.age = age;
          }
          getName() {
              return this.name;
          }
      }
      export default Person;
      
      //index.js
      import Person from './module02'
      const p=new Person('iskr',18);
      console.log(p.getName());
      

      需要注意的是export default 命令一个模块只允许出现一次。不过可以同时拥有多个export与一个export default

      //module03.js
      export function fn() {
          console.log('this is function name is fn');
      }
      export function bar() {
          console.log('hello bar');
      }
      class Person {
          constructor(name, age) {
              this.name = name;
              this.age = age;
          }
          getName() {
              return this.name;
          }
      }
      export default Person;
      
      //index.js
      import module01 from './module01'
      import Person,{bar, fn} from './moudle03'
      
      // 还可以直接全部引入 实战中很少用
      import * as moudle03 from './moudle03'
      
      // module03
      module03={
          fn:fn,
          bar:bar,
          default:Person
      }
      //执行调用
      var p=new Person('iskr',18);
            console.log(p.getName());//iskr
            fn();//this is function name is fn
      

      模块化开发

      //state.js
      const store = {
          // show: 0,
          // backgroundColor: '#ccc',
          // width: '200',
          // height: '200'
      }
      const events = {
          show: function () { },
          backgroundColor: function () { },
          width: function () { },
          height: function () { }
      };
      // 往store中添加一个状态值
      export const registerState = (status, value) => {
          if (store[status]) {
              throw new Error('此状态已存在');
          }
          store[status] = value;
          return value;
      }
      // 获取整状态树
      export const getStore = () => store;
      
      // 获取某个状态值
      export const getState = state => store[state];
      
      // 设置某个状态值
      export const setState = (state, value) => {
          store[state] = value;
          dispatch(state, value);
          return value;
      }
      
      // 可以理解为一个绑定的过程,因此命名为bind,在有的地方也称它为订阅
      export const bind = (status, eventFn) => {
          events[status] = eventFn;
      }
      // 移除绑定
      export const remove = status => {
          events[status] = null;
          return status;
      }
      // 需要在状态值改变时触发UI的变化,因此在setState方法中调用了该方法
      export const dispatch = (status, value) => {
          if (!events[status]) {
              throw new Error('未绑定任何事件!');
          }
          events[status](value);
          return value;
      }
      
      //registerState.js
      
      import { registerState } from './state'
      
      registerState('show', 0);
      registerState('backgroundColor', '#FFF');
      registerState('borderColor', '#000');
      registerState('width', 100);
      registerState('height',100);
      
      //utils.js
      
      export const getStyle = (obj, key) => {
          return obj.currentStyle ? obj.currentStyle[key] : document.defaultView.getComputedStyle(obj, false)[key];
      }
      
      //box.js
      import { bind } from './state'
      import { getStyle } from './utils'
      import './registerState'
      
      const div = document.querySelector('.target');
      
      bind('show', val => {
          if (val === 1) {
              div.classList.add('hide');
          }
          if (val === 0) {
              div.classList.remove('hide');
          }
      })
      
      bind('background', val => {
          div.style.backgroundColor = val;
      })
      
      
      bind('borderColor', val => {
          const width = parseInt(getStyle(div, 'boederWidth'));
          if (width === 0) {
              div.style.border = '2px solid #ccc';
          }
          div.style.borderColor=val;
      })
      
      bind('width',val=>{
          div.style.width=val;
      })
      
      bind('height',val=>{
          div.style.height=val;
      })
      
      //样式
      <div class="target">
          </div>
          <div class="control_wrap">
            <div>
              <button class="show">显示隐藏</button>
            </div>
            <div>
              <input type="text" class="bgcolor_input" placeholder="请输入BcakgroundColor">
              <button class="bgcolor_btn">确定</button>
            </div>
            <div>
              <input type="text" class="bdcolor_input" placeholder="请输入border-color">
              <button class="bdcolor_btn">确定</button>
            </div>
            <div>
              <span>width</span>
              <button class="width_j">-5</button>
              <input type="text" class="width_input" readonly>
              <button class="width_add">+5</button>
            </div>
            <div>
              <span>height</span>
              <button class="height_j">-5</button>
              <input type="text" class="height_input" readonly>
              <button class="height_add">+5</button>
            </div>
          </div>
          
          //操作逻辑
          // 显示隐藏
            const showbtn = document.querySelector('.show');
            showbtn.addEventListener('click', () => {
                if (getState('show') == 0) {
                    setState('show', 1);
                    console.log("隐藏");
                } else {
                    setState('show', 0);
                    console.log("显示");
                }
                box;
            }, false);
            // 设置背景颜色
            const bgcolor_input=document.querySelector('.bgcolor_input')
            const bg_btn=document.querySelector('.bgcolor_btn');
            bg_btn.addEventListener('click',()=>{
              if(bgcolor_input.value){
                setState('backgroundColor',bgcolor_input.value);
                console.log(bgcolor_input.value);
              }
            },false);
            // 设置boder的颜色
            const bdcolor_input=document.querySelector('.bdcolor_input')
            const bd_btn=document.querySelector('.bdcolor_btn');
            bd_btn.addEventListener('click',()=>{
              if(bdcolor_input.value){
                setState('borderColor',bdcolor_input.value);
              }
            },false);
      
            // 设置目标元素的宽度变化模块
            const wj_btn=document.querySelector('.width_j');
            const wa_btn=document.querySelector('.width_add');
            wj_btn.addEventListener('click',()=>{
              const getw=getState('width');
              if(getw>50){
                setState('width',getw-5);
              }
            });
             wa_btn.addEventListener('click',()=>{
              const getw=getState('width');
              if(getw<400){
                setState('width',getw+5);
              }
            });
      
            // 设置目标元素的高度变化模块
            const hj_btn=document.querySelector('.height_j');
            const ha_btn=document.querySelector('.height_add');
            const h_input=document.querySelector('.height_input')
            hj_btn.addEventListener('click',()=>{
              const geth=getState('height');
              if(geth>50){
                setState('height',getw-5);
                h_input.value=geth-5;
              }
            });
            ha_btn.addEventListener('click',()=>{
              const getw=getState('height');
              if(geth<400){
                setState('height',getw+5);
                h_input.value=geth+5;
              }
            });
      
      
      

防抖和节流

  • 函数防抖(debounce):其概念其实是从机械开关和继电器的“去弹跳”(debounce)衍生出来的,基本思路就是把多个信号合并为一个信号。比如一个 input 每当输入结束后两秒执行搜索事件,这就是个很经典的防抖需求。

  • 函数防抖的实现思路如下:将目标方法(动作)包装在setTimeout里面,然后这个方法是一个事件的回调函数,如果这个回调一直执行,那么这些动作就一直不执行。为什么不执行呢,我们设置了一个clearTimeout,这样setTimeout里的方法就不会执行! 为什么要clearTimeout呢,我们就需要将事件内的连续动作删掉嘛!待到用户不触发这事件了。那么setTimeout就自然会执行这个方法。

  • 防抖 debounce 多用于 “ 延迟多久后触发 ”

  • 具体用于input输入框架的格式验证,假如只是验证都是字母也罢了,太简单了,不怎么耗性能,如果是验证是否身份证,这性能消耗大,你可以隔170ms才验证一次。这时就需要这个东西。或者你这个是自动完全,需要将已有的输入数据往后端拉一个列表,频繁的交互,后端肯定耗不起,这时也需要这个,如隔 350ms 。 所以大致就有以下两种应用情景:

    1. 每次 resize/scroll 触发统计事件
    2. 文本输入的验证(连续输入文字后发送 AJAX 请求进行验证,验证一次就好)
    3. 表单提交按钮提交
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
        <style>
            .app {
                height: 200px;
                width: 100%;
                text-align: center;
                line-height: 200px;
                background-color: black;
                color: white;
            }
        </style>
    </head>
    <body>
        <div class="app"></div>
        <button class="btn">取消防抖</button>
        <script src="https://cdn.bootcdn.net/ajax/libs/underscore.js/1.11.0/underscore.min.js"></script>
        <script src="./debounce.js"></script>
        <script>
            let count = 0;
            var app = document.querySelector('.app');
            var btn = document.querySelector('.btn');
            console.log(app);
            function dosome(e) {
                // 解决参数问题
                console.log(e);
    
                // 解决this指向
                console.log(this);
                // 发请求
                count++
                app.innerHTML = count;
            }
            // app.onmousemove = function () {
            //     setTimeout(function () {
            //         dosome();
            //     }, 2000);
            // }
            // app.onmousemove = function(){dosome()};
            // 高阶函数 防抖
            var _de = debounce(dosome, 2000, false);
            // 取消防抖
            btn.onclick = function () {
                _de.cancel();
            }
            app.onmousemove = _de;//true延时前做操作  flase延时完后做操作
            // 防抖
            // 事件响应函数(doSomeThing)在一段时间后(300ms)才执行,如果在这段时间内再次调用,则重新计算执行时间;当预定的时间内没有再次调用该函数,则执行(doSomeThing)函数
            // 应用场景
            // 1.scroll事件滚动触发
            // 2.搜索框输入查询
            // 3.表单验证
            // 4.按钮提交事件
            // 5.浏览器窗口缩放,resuze事件
            // 解决事件频繁触发带来的困扰
        </script>
    </body>
    
    </html>
    
    // 防抖函数的封装
    // ES5
    function debounce(func, wait, immediate) {
        var timeout, callNow, resul;
        var debounce = function () {
            // 解决this指向问题
            var _this = this;
            // 把函数参数搞定
            var ars = arguments;
            // 每次返回前判断清除一下定时器
            if (timeout) clearTimeout(timeout);
            if (immediate) {
                callNow = !timeout;//不等于空true  
                timeout = setTimeout(function () {
                    timeout = null;
                }, wait);
                // 立即执行
                if (callNow) { resul = func.apply(_this, ars) }
            } else {
                // 不会立即执行
                timeout = setTimeout(function () {
                    // wait时后执行func函数
                    // func.call(_this,ars);这里更推荐使用apply
                    func.apply(_this, ars);
                }, wait);
            }
            return resul;
        }
        // 取消防抖
        debounce.cancel = function () {
            clearTimeout(timeout);
            timeout = null;
        }
        return debounce;
    }
    
  • 函数节流(throttle):节流的概念可以想象一下水坝,你建了水坝在河道中,不能让水流动不了,你只能让水流慢些。换言之,你不能让用户的方法都不执行。如果这样干,就是debounce了。为了让用户的方法在某个时间段内只执行一次,我们需要保存上次执行的时间点与定时器。

  • 函数节流的实现思路如下:某些代码不可以在没有间断的情况连续重复执行。第一次调用函数,创建一个定时器,在指定的时间间隔之后运行代码。当第二次调用该函数时,它会清除前一次的定时器并设置另一个。如果前一个定时器已经执行过了,这个操作就没有任何意义。然而,如果前一个定时器尚未执行,其实就是将其替换为一个新的定时器。目的是只有在执行函数的请求停止了一段时间之后才执行。

  • 节流 throttle 多用于 “ 多久触发一次 ”

  • 函数节流会用在比input, keyup更频繁触发的事件中,如resize, touchmove, mousemove, scroll。throttle 会强制函数以固定的速率执行。因此这个方法比较适合应用于动画相关的场景。

    • DOM 元素的拖拽功能实现(mousemove)
    • 射击游戏的 mousedown/keydown 事件(单位时间只能发射一颗子弹)
    • 计算鼠标移动的距离(mousemove)
    • Canvas 模拟画板功能(mousemove)
    • 搜索联想(keyup)
    • 监听滚动事件判断是否到页面底部自动加载更多:给 scroll 加了 debounce 后,只有用户停止滚动后,才会判断是否到了页面底部;如果是 throttle 的话,只要页面滚动就会间隔一段时间判断一次
    function throttle(func, wait, options) {
        var timeout, now, old = 0;
        if (!options) options = {};
        var throttle = function () {
            var _this = this;
            var args = arguments;
            now = new Date().valueOf();
            // 第一次就不会执行了
            if (options.leading === false && !old) {
                now = old;
            }
            if (now - old > wait) {
                if (timeout) { clearTimeout(timeout); timeout = null; }
                func.call(_this, args);
                old = now;
                // 判断来的training是true还是false
            } else if (!timeout && options.training === true) {
                timeout = setTimeout(function () {
                    func.call(_this, args);
                    old = new Date().valueOf();
                    timeout = null;
                }, wait);
            }
        }
        // throttle.cancel = function () {
        //     clearTimeout(timeout);
        //     timeout = null;
        //     now = 0;
        // }
        return throttle;
    }
    
    //调用代码
    	let count = 0;
            var app = document.querySelector('.app');
            var btn = document.querySelector('.btn');
            console.log(app);
            function dosome(e) {
                // 解决参数问题
                console.log(e);
    
                // 解决this指向
                console.log(this);
                // 发请求
                count++
                app.innerHTML = count;
            }
            // 高阶函数 防抖
            var _de = throttle(dosome, 3000, { leading: true, training: false });
            app.onmousemove = _de;//true延时前做操作  flase延时完后做操作
            btn.onclick = function () {
                _de.cancel();
            }
    
            // console.log(new Date().getTime());
            // console.log(new Date().valueOf());
    
    
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
JavaScript核心技术 简介: 《JavaScript核心技术》对于各种浏览器、浏览器版本、JavaScript版本、DOM版本的介绍,有助于我们理解所遇到的各种新旧代码,使我们能够对这些代码做出正确的取舍。《JavaScript核心技术》还提供了一些使用JavaScript的最佳实践。无论是新手还是老手,这些如何正确使用JavaScript的经验都能帮助他们养成良好的编程习惯。《JavaScript核心技术》还介绍了一些调试和开发JavaScript的工具,这些工具无疑能够提高我们的开发效率。 《JavaScript核心技术》最后对于Ajax和几个流行的JavaScript库的介绍,无疑会开阔我们在JavaScript使用上的思路。 《JavaScript核心技术》是一本真正意义上的“新书”,不仅介绍了最新的JavaScript知识和方向,还完全覆盖了当今Web开发中关于JavaScript的所有重要话题,它使用了大量实例代码,图文并茂地讲解了使用JavaScript的各个层次和领域的内容。它不是一本参考手册,但却是一本值得拥有的教程。 JavaScript核心技术 目录: 前言 第1章JavaScript初探 1.1规范和实现相互交织的历史 1.2跨浏览器的不兼容性和其他常见的JavaScript传说 1.3你能用JavaScript来做什么 1.4JavaScript初探:“HelloWorld!” 1.5JavaScript沙箱 1.6可访问性和JavaScript的最佳实践 第2章JavaScript数据类型与变量 2.1变量的标识 2.2作用域 2.3简单类型 2.4常量:有名称但不改变 2.5习题 第3章运算符和语句 3.1JavaScript语句的格式 3.2简单语句 3.3条件语句和程序流 3.4条件运算符 3.5逻辑运算符 3.6高级语句:循环语句 3.7习题 第4章JavaScript对象 4.1对象构造函数 4.2Number对象 4.3String对象 4.4正则表达式与RegExp 4.5有专门用途的对象:Date和Math 4.6JavaScript数组 4.7关联数组:不是数组的数组 4.8习题 第5章函数 5.1定义函数:细数所有方式 5.2回调函数 5.3函数和递归 5.4嵌套函数、函数闭包和内存泄漏 5.5作为对象的函数 5.6习题 第6章捕捉事件 6.1O级DOM上的事件句柄 6.22级DOM上的事件句柄 6.3产生事件 6.4习题 第7章表单与即时验证 7.1访问表单 7.2把事件附加在表单上:不同的方法 7.3选择列表 7.4单选按钮和复选框 7.5输入字段和JiT正则表达式 7.6习题 第8章沙箱及之上的cookie、连通性和隐私 第9章基础浏览器对象 第10章DOM:文档对象模型 第11章创建定制的JavaScript对象 第12章构建动态网页:在脚本中加入样式 第13章使用Ajax 第14章好消息:生动的程序库!令人惊异的Web服务!有趣的API! 附录习题答案
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值