#DEMO

这里输入demo描述,引用代码段 if (src.indexOf('/'+ cPathArr[0] +'/') !== -1),对关键词语加粗,引用链接


if (!window.console) {
    var names = "log debug info warn error assert dir dirxml group groupEnd time timeEnd count trace profile profileEnd".split(" ");
    window.console = {};
    for (var i = 0; i < names.length; ++i) window.console[names[i]] = function() {}
};

#碎片

基础优化技巧


function eventHandler(e) {
    //if (!e) e = window.event;
    e = e || window.event;
}
if (myobj) {
    doSomething(myobj);
}
可以替换为
myobj && doSomething(myobj);


// 多次操作在一个节点或者节点下子节点,对公共节点进行缓存
var myDiv = document.getElementById("myDiv");  
myDiv.style.left = myDiv.offsetLeft + myDiv.offsetWidth + "px";  


// for语句缓存终止条件
for(var i = 0, len = arr.length; i < len; i++){
    var item = arr[i];
    ...
}


// 使用容器存放临时变更,最后再一次性更新DOM
list.style.display = "none"; //先隐藏,因为隐藏的节点不会触发重排
var fragment = document.createDocumentFragment(); //创建空片段容器
for (var i=0; i < items.length; i++){
    var item = document.createElement("li");
    item.appendChild(document.createTextNode("Option " + i);
    fragment.appendChild(item); //每一次for最后把节点放入空片段容器
}
list.appendChild(fragment); //最后在页面插入容器
list.style.display = "";  //显示节点


// 遇到多次使用全局变量,先进行缓存
function search() {
    var location = window.location;
    location.foo();
    location.foo();
}


// 类型转换
var int = 5,
    str = int + '', //数字转字符
    int = str * 1; //字符转数字


// 往对象中插入新属性
var object = {name = 'franks', sex = 'man'}
object['age'] = '32';


//将条件分支,按可能性顺序从高到低排列,可以减少解释器对条件的探测次数.
//在同一条件子的多(>2)条件分支时,使用switch优于if:switch分支选择的效率高于if,在IE下尤为明显。4分支的测试,IE下switch的执行时间约为if的一半。
// 三目运算取代
var num = a > b ? a : b ;

#尾调用优化引用

函数调用会在内存形成一个"调用记录",又称"调用帧"(call frame),保存调用位置和内部变量等信息。如果在函数A的内部调用函数B,那么在A的调用记录上方,还会形成一个B的调用记录。等到B运行结束,将结果返回到A,B的调用记录才会消失。如果函数B内部还调用函数C,那就还有一个C的调用记录栈,以此类推。所有的调用记录,就形成一个"调用栈"(call stack)。

尾调用由于是函数的最后一步操作,所以不需要保留外层函数的调用记录,因为调用位置、内部变量等信息都不会再用到了,只要直接用内层函数的调用记录,取代外层函数的调用记录就可以了。


function f() {
  let m = 1;
  let n = 2;
  return g(m + n);
}
f();

// 等同于
function f() {
  return g(3);
}
f();

// 等同于
g(3);

上面代码中,如果函数g不是尾调用,函数f就需要保存内部变量m和n的值、g的调用位置等信息。但由于调用g之后,函数f就结束了,所以执行到最后一步,完全可以删除 f() 的调用记录,只保留 g(3) 的调用记录。

#尾递归优化

递归非常耗费内存,因为需要同时保存成千上百个调用记录,很容易发生"栈溢出"错误(stack overflow)。但对于尾递归来说,由于只存在一个调用记录,所以永远不会发生"栈溢出"错误。


function tailFactorial(n, total) {
  if (n === 1) return total;
  return tailFactorial(n - 1, n * total);
}

function factorial(n) {
  return tailFactorial(n, 1);
}

factorial(5) // 120

ES6函数默认值方法:


function factorial(n, total = 1) {
  if (n === 1) return total;
  return factorial(n - 1, n * total);
}

factorial(5) // 120

#打断循环应用


function elClickHandler() {
    ……
}
function init() {
    var el = document.getElementById('MyElement');
    el.onclick = elClickHandler; //上下文
}
init();

或

function init() {
    var el = document.getElementById('MyElement');
    el.onclick = function () {
        ……
    }
    el = null; // 对象设为空,事件不再执行
}
init();

#作用域链


function foo() {
    var val = 'hello';
    function bar() {
        function baz() {
            global.val = 'world;'
        }
        baz();
        console.log(val); //=> hello
    }
    bar();
}
foo();

baz()函数的执行在全局作用域中定义了一个全局变量val。而在bar()函数中,对val这一标识符进行访问时,按照从内到外厄德查找原则:在bar函数的作用域中没有找到,便到上一层,即foo()函数的作用域中查找。本次标识符访问在foo()函数的作用域中找到了符合的变量,便不会继续向外查找,故在baz()函数中定义的全局变量val并没有在本次变量访问中产生影响。

#闭包

闭包就是能够读取其他函数内部变量的函数


function foo() {
    var local = 'Hello';
    function foo2(){
        return local;
    }
    return foo2;
}
var result = foo();
console.log(result()); //=> Hello

闭包可以用在许多地方。它的最大用处有两个,一个是可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。


  function foo(){
    var n = 999;
    nAdd = function(){ n += 1} //定义一个全局变量,使用了函数内的局部变量
    function foo2(){
      alert(n); //返回上级函数变量
    }
    return foo2; //返回下级函数
  }
  var result=foo();
  result(); //=> 返回变量n默认值999
  nAdd(); // 执行了一次n+1, n值变为1000
  result(); //=> 返回变量n的值1000

在这段代码中,result实际上就是闭包foo2函数。它一共运行了两次,第一次的值是999,第二次的值是1000。这证明了,函数foo中的局部变量n一直保存在内存中,并没有在foo调用后被自动清除。

为什么会这样呢?原因就在于foo是foo2的父函数,而foo2被赋给了一个全局变量,这导致foo2始终在内存中,而foo2的存在依赖于foo,因此foo也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。

这段代码中另一个值得注意的地方,就是"nAdd=function(){n+=1}"这一行,首先在nAdd前面没有使用var关键字,因此nAdd是一个全局变量,而不是局部变量。其次,nAdd的值是一个匿名函数(anonymous function),而这个匿名函数本身也是一个闭包,所以nAdd相当于是一个setter,可以在函数外部对函数内部的局部变量进行操作。


var uniqueInteger = (function() {
    var counter = 0;
    return function() {
        return counter++;
    }
}());
uniqueInteger(); //=>0
uniqueInteger(); //=>1
uniqueInteger(); //=>2

var makeCounter = function() {
  var privateCounter = 0;
  function changeBy(val) {
    privateCounter += val;
  }
  return {
    increment: function() {
      changeBy(1);
    },
    decrement: function() {
      changeBy(-1);
    },
    value: function() {
      return privateCounter;
    }
  }  
};

var Counter1 = makeCounter();
var Counter2 = makeCounter();
console.log(Counter1.value()); /* logs 0 */
Counter1.increment();
Counter1.increment();
console.log(Counter1.value()); /* logs 2 */
Counter1.decrement();
console.log(Counter1.value()); /* logs 1 */
console.log(Counter2.value()); /* logs 0 */

function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
}
MyObject.prototype = {
  getName: function() {
    return this.name;
  },
  getMessage: function() {
    return this.message;
  }
};
var newobj1=MyObject('x','hello');
var newobj2=MyObject('y','javascript');
newobj1.getName()==newobj2.getName();//true
或
function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
}
MyObject.prototype.getName = function() {
  return this.name;
};
MyObject.prototype.getMessage = function() {
  return this.message;
};

<\p id="help">Helpful notes will appear here
<\p>E-mail: <\input type="text" id="email" name="email">
<\p>Name: <\input type="text" id="name" name="name">
<\p>Age: <\input type="text" id="age" name="age">

unction showHelp(help) {
  document.getElementById('help').innerHTML = help;
}

function makeHelpCallback(help) {
  return function() {
    showHelp(help);
  };
}

function setupHelp() {
  var helpText = [
      {'id': 'email', 'help': 'Your e-mail address'},
      {'id': 'name', 'help': 'Your full name'},
      {'id': 'age', 'help': 'Your age (you must be over 16)'}
    ];

  for (var i = 0; i < helpText.length; i++) {
    var item = helpText[i];
    document.getElementById(item.id).onfocus =
       makeHelpCallback(item.help);
  }
}

setupHelp();

#闭包管理

有六个按钮,分别对应六种事件,当用户点击按钮时,在指定的地方输出相应的事件。


var btns = document.querySelectorAll('.btn'); // 6 elements
var output = document.querySelector('#output');
var events = [1, 2, 3, 4, 5, 6];

// Case 1
for (var i = 0, l = btns.length; i < l; i++) {
  btns[i].onclick = function(evt) {
    output.innerText += 'Clicked ' + events[i];
  };
}

// Case 2
for (var i = 0, l = btns.length; i < l; i++) {
  btns[i].onclick = (function(index) {
    return function(evt) {
      output.innerText += 'Clicked ' + events[index];
    };
  })(i);
}

// Case 3
for (var i = 0, l = btns.length; i < l; i++) {
  btns[i].onclick = (function(event) {
    return function(evt) {
      output.innerText += 'Clicked ' + event;
    };
  })(events[i]);
}

这里第一个解决方案显然是典型的循环绑定事件错误,这里不细说,详细可以参照我给一个网友的回答;而第二和第三个方案的区别就在于闭包传入的参数。

第二个方案传入的参数是当前循环下标,而后者是直接传入相应的事件对象。事实上,后者更适合在大量数据应用的时候,因为在JavaScript的函数式编程中,函数调用时传入的参数是基本类型对象,那么在函数体内得到的形参会是一个复制值,这样这个值就被当作一个局部变量定义在函数体的作用域内,在完成事件绑定之后就可以对events变量进行手工解除引用,以减轻外层作用域中的内存占用了。而且当某个元素被删除时,相应的事件监听函数、事件对象、闭包函数也随之被销毁回收。

#eval预编译


function square(input) {
  var output;
  eval('output=(input * input)');
  return output;
}

eval 方法计算平方值并输出结果,但性能不好。此例使用字符串 output=(input*input) 作为 eval 方法的参数,无法利用 JavaScript 预编译。


function square(input) {
  var output;
  eval(new function() { output=(input * input)});
  return output;
}

使用函数代替字符串作参数确保新方法中的代码能被 JavaScript 编译器优化。