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.sort(compareFunction)**:compareFunction可选,用来指定按某种顺序进行排列的函数。如果简单,元素以转换为的字符串的各国个字符的Unicode位点进行排序。比如:

    [1, 10, 21, 2].sort();//[1, 10, 2, 21]
    [1, 10, 21, 2].sort((a, b) => a - b);//[1, 2, 10, 21]
    
    // 需要被排序的数组
    var list = ['Delta', 'alpha', 'CHARLIE', 'bravo'];
    
    // 对需要排序的数字和位置的临时存储
    var mapped = list.map(function(el, i) {
      return { index: i, value: el.toLowerCase() };
    })
    
    // 按照多个值排序数组
    mapped.sort(function(a, b) {
      return +(a.value > b.value) || +(a.value === b.value) - 1;
    });
    
    // 根据索引得到排序的结果
    var result = mapped.map(function(el){
      return list[el.index];
    });
    

     

  13. 数组去重新

    //方法一:先排序后去重
    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)];
    }
    
  14. 求数组最可怜最小数

    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
    
  15. encodeURI() 与 encodeURIComponent():它们与escape()一样都未会见针对
    ASCII 字母和数字进行编码,但escape()除了不对前述字符和“* @ – _ + .
    / ”这些 ASCII
    标点符号进行编码外,其他所有的字符都见面叫转义序列替换 。关于URL的正经定义可参照: Uniform
    Resource Locators
    (URL)

    1. encodeURI()对 URI 进行完整的编码,但不会对“;/?:@&=+$,#”这些在 URI 中具有特殊含义的 ASCII 标点符号进行编码,同时也不编码“- _ . ! ~ * ' ( )”这些ASCII字符;
    
    2. encodeURIComponent()可把字符串作为 URI 组件进行编码,会对“;/?:@&=+$,#”这些在 URI 中具有特殊含义的 ASCII 标点符号进行编码,但不编码“- _ . ! ~ * ' ( )”这些ASCII字符。
    
  16. setTimeout与setInterval: setTimeout 函数会于一个时刻段过去后以班中上加一个信,这个时段作为函数的第二单参数为盛传。如果队列中并未其他消息,消息会让立处理;如果起其它消息,setTimeout消息必须等其他消息处理完。因此次独参数就意味着最好少之日一旦无确切的时。

    浏览器的内核是多线程的,它们在内核控制下相互配合以保持同步,一个浏览器至少实现三个常驻线程:JavaScript引擎线程、GUI渲染线程、事件触发线程
    
    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. 延迟只是表明可能的最小延迟时间,而不保证确切延迟时间。如果任务队列很长,耗时很多,最后延迟时间可能远远超出参数中指定的值
    
  17. 原型链(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"
    
  18. 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函数的返回值 
    
  19. 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]
    
  20. 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;   
    } 
    
  21. 闭包(Closure):词法表示包括无让计算的变量(上下文环境遭到变量,非函数参数)的函数,函数可以使用函数之外定义之变量。下面坐单例模式为例来讲述如何创建闭包

    let singleton = function(){
      let obj;
      return function(){
           return obj || (obj = new Object());
      }
    }();
    
  22. New
    Function()
    :如果要就此一个配串来新建一个函数,用函数参数来指定this对象:

    let variables = {
        key1: 'value1',
        key2: 'value2'
    },
        fnBody = 'return this.key1 + ":" + this.key2',
        fn = new Function(fnBody);
    console.log(fn.apply(variables));
    
  23. 高阶函数:如果函数定义之一对还是整参数是另一个函数,或者返回值是任何一个函数,即可认为该函数凡是高阶函数,如函数柯里化(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
    
  24. 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);
    
  25.  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新增加的
        })
    
  26. 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.

     ECMAScript 1

    (1) 事件捕捉(Capturing
    Phase):event通过target的祖宗从window传播到目标的父节点。IE不支持Capturing
    (2) 目标等(Target
    Phase):event到达event之target。如果事件类指示事件非冒泡,则event在拖欠等完成后拿停止
    (3) 事件冒泡(Bubbling
    Phase):event因反的次第以对象祖先中传播,从target的父节点开始,到window结束

事件绑定:

    1. Tag事件属性绑定:<button onclick="do some things"></button>
    2. 元素Node方法属性绑定:btnNode.onclick = function(){//do some things}
    3. 注册EventListener:btnNode.addEventListener('click', eventHandler, bubble);另外,IE下实现与W3C有点不同,btnNode.attachEvent(‘onclick’, eventHandler)
  1. 递归与栈溢出(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
  1.  预取与延迟加载:预取如提前加载图片,当用户用查阅时可一直从地面渲染,而休需要去服务端下载,它保证了图迅速、无缝地宣布,让用户以浏览时收获重新好的用户体验;延迟加载如优先加载小图或示例图作为占位图,只有上浏览器可张区域外时,才加载完整图片并展示出来,在页面图片数多且比深之早晚怪实用

  2. 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
    字ECMAScript面意思是省去流阀,限制监听函数执行的效率,与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)
        }
      }
    }