JavaScript 开发总结(一)

  1. 数据类型:JavaScript定义的数据类型有字符串、数字、布尔、数组、对象、Null、Undefined,但typeof有区分可判别的数据分类是number、string、boolean、object(null / array)、function和undefined。undefined 这个值表示变量不含有值,null 可以用来清空变量
    let a = 100;
    typeof a;//number
    a = undefined;
    typeof a;//undefined
    a = null;
    typeof a;//object
    a instanceof Object;//false. instanceof 运算符检测 Object.prototype 是否存在于 a 的原型链上
  2. 默认类型转换:这是个让人头疼的问题,对于“==”最好换成“===”、“!=”最好换成“!==”,以防止默认类型转换而导致逻辑错误。这里列举一部分
    5 == true;//false。true会先转换为1,false转换为0
    '12' + 1;//'123'。数字先转换成字串
    '12' - 1;//11。字串先转换成数字
    [] == false;//true。数组转换为其他类型时,取决于另一个比较者的类型
    [] == 0;//true
    [2] == '2';//true
    [2] == 2;//true
    [] - 2;//-2
    12 + {a:1};//"12[object Object]"。这里对象先被转换成了字串
    null == false;//false
    null == 0;//false
    null - 1;//-1
  3. Number计算精度问题:在含有小数部分的数值运算中可能会存在精度问题,如0.03 - 0.02 = 0.009999999999999998,结果不是0.01,这在金额计算中将是一个很大的问题。当然金额计算通常放在后端来做,但同样的问题恐怕依旧难以避免,所以要么以分而不是元为单位进行计算,要么先将值扩大倍数以整数进行计算然后结果缩小相应倍数。总之,这两种方法的本质都是一样的,那就是通过整数运算来避免运算精度问题。
  4. JS对象与字串互转换:如果要复制对象属性,可通过JSON.stringify()转换成字符串类型,赋值给复制变量后再通过JSON.parse()转换成对象类型,但这种转换会导致原对象方法丢失,只有属性可以保留下来;如果在发生对象赋值后再赋新值,其将指向新的地址空间。关于JSON与JavaScript之间的关系:JSON基于 JavaScript 语法,但不是JavaScript 的子集
  5. 大小写切换
    let str = '23abGdH4d4Kd';
    str = str.replace(/([a-z]*)([A-Z]*)/g, function(match, $1 , $2){return $1.toUpperCase() + $2.toLowerCase()});//"23ABgDh4D4kD"
    
    str = 'web-application-development';
    str = str.replace(/-([a-z])/g, (replace)=>replace[1].toUpperCase());//"webApplicationDevelopment"(驼峰转换)
  6. 生成随机数
    str = Math.random().toString(36).substring(2) 
  7. | 0 、^ 0、>>、 ~~ 取整数去除小数部分:这里没有列出& 1,是因为其操作无法保持符号不变
    3.2 / 1.7 | 0 = 1;
    ~~(3.2 / 1.7) = 1;//~~可还原原数
    3.2 / 1.7 >> 0 = 1;
    3.2 / 1.7 ^ 0 = 1;
    -3.2 / 1.7 & 1 = 1;//& 1操作无法保持符号不变
    if (!~str.indexOf(substr)) {//~按位取反,等价于符号取反然后减一
      //do some things
    }
  8. 整数判断:对于parseInt(010,10)结果值为8,parseInt("010",10)结果值为10,这说明parseInt会自动判定数值基数并优先于用户定义。0x10+''结果为字串"16",010+''结果为字串"8"
    let a = 2,
        b = 2.3;
    
    //方法一:Math.round()
    Math.round(a) === a;//true
    Math.round(b) === b;//false
    
    //方法二:模1运算
    typeof a == "number" && (a % 1) === 0;//true
    typeof b == "number" && (b % 1) === 0;//false
    
    //方法三:ES6 Number.isInteger()
    Number.isInteger(a);//true
    Number.isInteger(b);//false
    
    //方法四:parseInt()
    parseInt(a, 10) === a;//true
    parseInt(b, 10) === b;//false
    
    //方法五:异或0运算
    a ^ 0 === a;//true
    b ^ 0 === b;//false
  9. NOT(!)、AND(&&) 和 OR(||):OR(||) 和 AND(&&) 运算结果是运算终止时当前运算数(表达式)的值,结果不一定是true或false;但是NOT(!)运算结果要么是true,要么是false。比较难理解的是[ ] == ![ ]
    true && 0;//0
    false && 2;//false
    1 && 2;//2
    1 || 2;//1
    0 || 1;//1
    ![] || !{} || 3;//3
    [] == ![];//true
    !NaN && !undefined && !null && !0 && !'' && 6;//6
    
  10. 无第三变量交换值:下边的做法未必在空间和时间上都比声明第三变量来的更好,写在这里仅仅为了拓展一下思维
    //方法一:通过中间数组完成交换
    let a = 1,
        b = 2;
    a = [b, b = a][0];
    
    //方法二:通过加减运算完成交换
    let a = 1,
        b = 2;
    a = a + b;
    b = a - b;
    a = a - b;
    
    //方法三:通过加减运算完成交换
    let a = 1,
        b = 2;
    a = a - b;
    b = a + b;
    a = b - a;
    
    //方法四:通过两次异或还原完成交换。另外,这里不会产生溢出
    let a = 1,
        b = 2;
    a = a ^ b;
    b = a ^ b;
    a = a ^ b;
    
    //方法五:通过乘法运算完成交换
    let a = 1,
        b = 2;
    a = b + (b = a) * 0; 
    
    //方法六:通过ES6解构赋值
    let a = 1,
        b = 2;
    [a, b] = [b, a];
  11. /、%、**运算符

    5 / 3;//1.6666666666666667
    4.53 % 2;//0.5300000000000002
    5 % 3;//2
    2 ** 3;//8,相比于Math.pow(2,3)要简洁多了
  12. 数组去重
    //方法一:先排序后去重
    Array.prototype.unique = function(){
        this.sort(); //先排序
        let res = [this[0]];
        for(let i = 1; i < this.length; i++){
            if(this[i] !== res[res.length - 1]){
                res.push(this[i]);
            }
        }
        
        return res;
    }
    
    //方法二:利用对象属性唯一性避免重复元素
    Array.prototype.unique = function(){
        let res = [],
            deduplication = {};
        for(let i = 0; i < this.length; i++){
            if(!deduplication[this[i]]){
                res.push(this[i]);
                deduplication[this[i]] = 1;
            }
        }
    
        return res;
    }
    
    //方法三:利用过滤函数递代去重处理,该方法可保持数组原序不变
    Array.prototype.unique = function () {
        let self = this,
            res = [],
            target;
        while (self.length) {
            target = self.shift();
            res.push(target);
            self = self.filter(item => target !== item);
        }
    
        return res;
    }
    
    //方法四:利用ES6新增加的Set数据类型做中间转换可去重
    Array.prototype.unique = function () {
        return [...new Set(this)];
    }
  13. 求数组最大最小数
    Math.max.apply(null, [2,5,30,1,20]);//30
    Math.min.apply(null, [2,5,30,1,20]);//1
    Math.min(...[2,5,30,1,20]);//1.ES6
    
  14. encodeURI() 与 encodeURIComponent():它们和escape()一样都不会对 ASCII 字母和数字进行编码,但escape()除了不对前述字符和“* @ - _ + . / ”这些 ASCII 标点符号进行编码外,其他所有的字符都会被转义序列替换 。关于URL的标准定义可参考: Uniform Resource Locators (URL)
    1. encodeURI()对 URI 进行完整的编码,但不会对“;/?:@&=+$,#”这些在 URI 中具有特殊含义的 ASCII 标点符号进行编码,同时也不编码“- _ . ! ~ * ' ( )”这些ASCII字符;
    
    2. encodeURIComponent()可把字符串作为 URI 组件进行编码,会对“;/?:@&=+$,#”这些在 URI 中具有特殊含义的 ASCII 标点符号进行编码,但不编码“- _ . ! ~ * ' ( )”这些ASCII字符。
  15. setTimeout与setInterval
    浏览器的内核是多线程的,它们在内核控制下相互配合以保持同步,一个浏览器至少实现三个常驻线程:JavaScript引擎线程、GUI渲染线程、事件触发线程
    
    .JavaScript引擎线程是基于事件驱动单线程执行的,JavaScript引擎一直等待着任务队列中任务的到来,然后加以处理,浏览器无论什么时候都只有一个JavaScript线程在运行JavaScript程序。
    
    .GUI渲染线程负责渲染浏览器界面,当界面需要重绘(Repaint)或由于某种操作引发回流(Reflow)时,该线程就会执行。但需要注意,GUI渲染线程与JavaScript引擎是互斥的,当JavaScript引擎执行时GUI线程会被挂起,GUI更新会被保存在一个队列中等到JavaScript引擎空闲时立即被执行。
    
    .事件触发线程在一个事件被触发时该线程会把事件添加到待处理队列的队尾,等待JavaScript引擎的处理。这些事件可来自JavaScript引擎当前执行的代码块如setTimeout、也可来自浏览器内核的其他线程如鼠标点击、Ajax异步请求等,但由于JavaScript的单线程关系所有这些事件都得排队等待JavaScript引擎处理(当线程中没有执行任何同步代码的前提下才会执行异步代码)。
    1. setTimeout()和setInterval()共用一个编号池,clearTimeout()和 clearInterval() 在技术上可以互换;但是为了避免混淆,不要混用取消定时函数
    2. 需要注意的是setTimeout()和setInterval()延迟执行的函数或代码中的this会指向一个错误的值,因为那些函数或代码是在一个分离的环境空间里被执行的,这些代码中包含的 this 关键字在非严格模式会指向 window (或全局)对象,严格模式下为 undefined
    3. IE < 9环境下setTimeout() 或者 setInterval()都不支持传递额外的参数,可以考虑自行重新定义兼容代码同名取代这两个函数
    4. setTimeout()的最小延迟时间与浏览器及操作系统有关,但不是0。在John Resig的《Javascript忍者的秘密》中:Browsers all have a 10ms minimum delay on OSX and a(approximately) 15ms delay on Windows.另外Firefox中定义的最小时间间隔(DOM_MIN_TIMEOUT_VALUE)是10毫秒,HTML5定义的最小时间间隔是4毫秒
    5. 延迟只是表明可能的最小延迟时间,而不保证确切延迟时间。如果任务队列很长,耗时很多,最后延迟时间可能远远超出参数中指定的值
  16. 原型链(Prototype Chaining)与继承: 原型链是ECMAScript 中实现继承的方式。JavaScript 中的继承机制并不是明确规定的,而是通过模仿实现的,所有的继承细节并非完全由解释程序处理,你有权决定最适用的继承方式,比如使用对象冒充(构造函数定义基类属性和方法)、混合方式(用构造函数定义基类属性,用原型定义基类方法)
    function ClassA(sColor) {
        this.color = sColor;
    }
    
    ClassA.prototype.sayColor = function () {
        alert(this.color);
    };
    
    function ClassB(sColor, sName) {
        ClassA.call(this, sColor);
        this.name = sName;
    }
    
    ClassB.prototype = new ClassA();
    
    ClassB.prototype.sayName = function () {
        alert(this.name);
    };
    
    var objA = new ClassA("blue");
    var objB = new ClassB("red", "John");
    objA.sayColor();    //输出 "blue"
    objB.sayColor();    //输出 "red"
    objB.sayName();    //输出 "John"
  17. Object.creat()和Object.assign():Object.create(proto, [ propertiesObject ])可用来实现类式单继承,从指定的原型对象及其属性去创建一个新的对象;Object.assign(target, ...sources)用于将所有可枚举的属性的值从一个或多个源对象复制到目标对象。另外,JSON.parse(JSON.stringify(obj))也可以用来实现对obj的复制。
    var obj = {
      foo: 1,
      get bar() {
        return 2;
      }
    };
    
    var copy = Object.assign({}, obj); 
    // { foo: 1, bar: 2 }
    // copy.bar的值来自obj.bar的getter函数的返回值 
  18. call、apply和bind:call和apply的用途都是用来调用当前函数,且用法相似,它们的第一个参数都用作 this 对象,但其他参数call要分开列举,而apply要以一个数组形式传递;bind给当前函数定义预设参数后返回这个新的函数(初始化参数改造后的原函数拷贝),其中预设参数的第一个参数是this指定(当使用new 操作符调用新函数时,该this指定无效),新函数调用时传递的参数将位于预设参数之后与预设参数一起构成其全部参数,bind最简单的用法是让一个函数不论怎么调用都有同样的 this 值。下边的list()也称偏函数(Partial Functions):

    function list() {
      return Array.prototype.slice.call(arguments);
    }
    
    var list1 = list(1, 2, 3); // [1, 2, 3]
    
    // Create a function with a preset leading argument
    var leadingThirtysevenList = list.bind(undefined, 37);
    
    var list2 = leadingThirtysevenList(); // [37]
    var list3 = leadingThirtysevenList(1, 2, 3); // [37, 1, 2, 3]
  19. Memoization技术: 替代函数中太多的递归调用,是一种可以缓存之前运算结果的技术,这样我们就不需要重新计算那些已经计算过的结果。In computing, memoization or memoisation is an optimization technique used primarily to speed up computer programs by storing the results of expensive function calls and returning the cached result when the same inputs occur again. Although related to caching, memoization refers to a specific case of this optimization, distinguishing it from forms of caching such as buffering or page replacement. In the context of some logic programming languages, memoization is also known as tabling.

    function memoizer(fundamental, cache) {   
      let cache = cache || {},   
          shell = function(arg) {   
              if (! (arg in cache)) {   
                  cache[arg] = fundamental(shell, arg);   
              }   
              return cache[arg];   
          };   
      return shell;   
    } 
  20. 闭包(Closure):词法表示包括不被计算的变量(上下文环境中变量,非函数参数)的函数,函数可以使用函数之外定义的变量。下面以单例模式为例来讲述如何创建闭包
    let singleton = function(){
      let obj;
      return function(){
           return obj || (obj = new Object());
      }
    }();
  21. New Function():如果要用一个字串来新建一个函数,用函数参数来指定this对象:
    let variables = {
        key1: 'value1',
        key2: 'value2'
    },
        fnBody = 'return this.key1 + ":" + this.key2',
        fn = new Function(fnBody);
    console.log(fn.apply(variables));
  22. 高阶函数:如果函数定义的部分或全部参数是另一个函数,或者返回值是另一个函数,即可认为该函数是高阶函数,如函数柯里化(currying)和反柯里化(uncurrying)。柯里化是把接受多个参数的函数转换成接受部分参数的函数,并在该函数中返回接受余下的参数且返回结果的新函数的技术,它可用于延迟计算到最后一次调用
    var currying = function (fn) {
        var _args = [];
        return function () {
            if (arguments.length === 0) {
                return fn.apply(this, _args);
            }
            Array.prototype.push.apply(_args, [].slice.call(arguments));
            return arguments.callee;
        }
    };
    
    var add=function () {
        var total = 0;
        for (var i = 0, c; c = arguments[i++];) {
            total += c;
        }
        return total;
    };
    
    var sum = currying(add);  
      
    sum(100,200)(300);//ƒ () { ... }
    sum(400);//ƒ () { ... }
    sum();//1000
    反柯里化是把函数签名形式obj.func(arg1, arg2)转化成新的函数签名形式func(obj, arg1, arg2),这样后者比前者更具有一般适应性,而不再是特殊对象方法
    Function.prototype.uncurrying = function (){
        var _this = this;
        return function (){
            return Function.prototype.call.apply(_this,arguments);
        }
    };
    
    function fn(){
        console.log(`Hello ${this.value},${[].slice.call(arguments)}`);
    }
    
    var uncurryfn = fn.uncurrying();
    
    uncurryfn({value:'World'},'Javascript');  //Hello World,Javascript
  23. DocumentFragment: Roughly speaking, a DocumentFragment is a lightweight container that can hold DOM nodes. 在维护页面DOM树时,使用文档片段document fragments 通常会起到优化性能的作用

    let ul = document.getElementsByTagName("ul")[0],
        docfrag = document.createDocumentFragment();
    
    const browserList = [
        "Internet Explorer", 
        "Mozilla Firefox", 
        "Safari", 
        "Chrome", 
        "Opera"
    ];
    
    browserList.forEach((e) => {
        let li = document.createElement("li");
        li.textContent = e;
        docfrag.appendChild(li);
    });
    
    ul.appendChild(docfrag);
  24.  forEach()遍历:另外,适当时候也可以考虑使用for 或 for ... in 或 for ... of 语句结构

    1. 数组实例遍历: arr.forEach(function(item, key){
            //do some things
        })
    2. 非数组实例遍历: Array.prototype.forEach.call(obj, function(item, key){
            //do some things
        })
    3. 非数组实例遍历: Array.from(document.body.childNodes[0].attributes).forEach(function(item, key){
            //do some things. Array.from()是ES6新增加的
        })
  25. DOM事件流:Graphical representation of an event dispatched in a DOM tree using the DOM event flow.If the bubbles attribute is set to false, the bubble phase will be skipped, and if stopPropagation() has been called prior to the dispatch, all phases will be skipped.

     JavaScript 开发总结(一)_第1张图片

    (1) 事件捕捉(Capturing Phase):event通过target的祖先从window传播到目标的父节点。IE不支持Capturing
    (2) 目标阶段(Target Phase):event到达event的target。如果事件类型指示事件不冒泡,则event在该阶段完成后将停止
    (3) 事件冒泡(Bubbling Phase):event以相反的顺序在目标祖先中传播,从target的父节点开始,到window结束


    事件绑定:

    1. Tag事件属性绑定:
    2. 元素Node方法属性绑定:btnNode.onclick = function(){//do some things}
    3. 注册EventListener:btnNode.addEventListener('click', eventHandler, bubble);另外,IE下实现与W3C有点不同,btnNode.attachEvent(‘onclick’, eventHandler)
  26. 递归与栈溢出(Stack Overflow):递归非常耗费内存,因为需要同时保存成千上百个调用帧,很容易发生“栈溢出”错误;而尾递归优化后,函数的调用栈会改写,只保留一个调用记录,但这两个变量(func.arguments、func.caller,严格模式“use strict”会禁用这两个变量,所以尾调用模式仅在严格模式下生效)就会失真。在正常模式下或者那些不支持该功能的环境中,采用“循环”替换“递归”,减少调用栈,就不会溢出

    function Fibonacci (n) {
      if ( n <= 1 ) {return 1};
    
      return Fibonacci(n - 1) + Fibonacci(n - 2);
    }
    Fibonacci(10); // 89
    Fibonacci(50);// 20365011074,耗时10分钟左右才能出结果
    Fibonacci(100);// 这里就一直出不了结果,也没有错误提示
    
    
    function Fibonacci2 (n , ac1 = 1 , ac2 = 1) {
      if( n <= 1 ) {return ac2};
    
      return Fibonacci2 (n - 1, ac2, ac1 + ac2);
    }
    Fibonacci2(100) // 573147844013817200000
    Fibonacci2(1000) // 7.0330367711422765e+208
    Fibonacci2(10000) // Infinity. "Uncaught RangeError:Maximum call stack size exceeded"

    可见,经尾递归优化之后,性能明显提升。如果不能使用尾递归优化,可使用蹦床函数(trampoline)将递归转换为循环:蹦床函数中循环的作用是申请第三者函数来继续执行未完成的任务,而保证自己函数可以顺利退出。另外,这里的第三者和自己可能是同一函数定义

    function trampoline(f) {
      while (f && f instanceof Function) {
        f = f();
      }
      return f;
    }
    
    //改造Fibonacci2()的定义
    function Fibonacci2 (n , ac1 = 1 , ac2 = 1) {
      if( n <= 1 ) {return ac2};
    
      return Fibonacci2.bind(undefined, n - 1, ac2, ac1 + ac2);
    }
    
    trampoline(Fibonacci2(100));//573147844013817200000
  27.  预取与延迟加载:预取如提前加载图片,当用户需要查看时可直接从本地渲染,而不需要去服务端下载,它保证了图片快速、无缝地发布,让用户在浏览时获得更好的用户体验;延迟加载如先加载小图或示例图作为占位图,只有进入浏览器可视区域内时,才加载完整图片并显示出来,在页面图片数量多且比较大的时候非常有用

  28. Debounce 和 Throttle:Debounce 字面意思是去弹跳消除抖动,强制某个连续动作只触发一次,避免多次触发。我们希望在用户停止某个操作一段时间之后才执行相应的监听函数,而不是在用户操作的过程当中,就执行多少次监听函数。比如你在 3s 内连续地移动了鼠标,浏览器会触发多个 mousemove 事件,执行多次监听函数;但如果对监听函数用 100ms 的Debounce去弹跳后,那么浏览器只会在第 3.1s 的时候执行这个监听函数一次。下边是Debounce的实现:
    /**
    *
    * @param fn {Function}   该函数定义了事件触发后的业务处理逻辑
    * @param delay {Number}  延迟时间,单位是毫秒(ms)
    *
    * @return {Function}     目标事件将要触发执行的监听函数
    */
    function debounce(fn, delay) {
      // 定时器,用来 setTimeout
      let timer
      // 返回一个“去弹跳”了的监听函数
      return function () {
        // 保存函数调用时的上下文和参数,传递给 fn
        let context = this,
            args = arguments;
        // 先清除定时器,保证 delay 毫秒内 fn 不执行多次
        clearTimeout(timer)
        // 用户停止当前连续操作后的第 delay 毫秒执行 fn
        timer = setTimeout(function () {
          fn.apply(context, args)
        }, delay)
      }
    }
    Throttle 字面意思是节流阀,限制监听函数执行的频率,与Debounce不同的是,它不是要避免连续动作引起监听函数多次执行:
    /**
    *
    * @param fn {Function}   该函数定义了事件触发后的业务处理逻辑
    * @param delay {Number}  执行间隔,单位是毫秒(ms)
    *
    * @return {Function}     目标事件将要触发执行的监听函数
    */
    function throttle(fn, threshhold) {
      // 记录上次执行的时间
      let last,
      // 定时器
          timer;
      // 默认间隔为 250ms
      threshhold || (threshhold = 250);
      // 返回一个“经节流”了的监听函数
      return function () {
        // 保存函数调用时的上下文和参数,传递给 fn
        let context = this,
            args = arguments,
            now = +new Date();
        // 如果距离上次执行 fn 函数的时间小于 threshhold,那么就放弃
        // 执行 fn,并重新计时
        if (last && now < last + threshhold) {
          clearTimeout(timer)
          timer = setTimeout(function () {
            last = now
            fn.apply(context, args)
          }, threshhold)
        } else {// 在连续动作刚开始或超过threshhold间隔时立即执行 fn
          last = now
          fn.apply(context, args)
        }
      }
    }

     

你可能感兴趣的:(Javascript)