1

大话js

先给大家意淫一下前端的futrue.
目前的JS应该是工业革命时代了。以前js就只是作为什么移动一下图片,更换一下背景颜色等, 比较废的活. 但是现在h5,nodeJS,ECMA-6,gulp,grunt,webpack,react...等等各种框架,工具的出现,基本上颠覆了js的角色。以前js就是作为一个配角,html+CSS才是主要的,现在,你说你写网页的不会用js,呵呵,U ~= 渣渣. 而且庸俗一点来说,现在前端的薪资range应该是其他技术类职业变化最大的,年薪2w+ 到 200w+ 的都有。所以js对于前端来说莫过于一块金子招牌,如果你js真的吃透了,相信你的路真的是太宽了~
意淫完毕,该谈正事了,JS最佳实践应该是很多地方都会提到的。我这里引用的是高级程序设计里面的内容,如有出错的地方,欢迎纠正.

类型注释

由于js是一门弱类型的语言,所以往往造成了类型的不清楚,有可能胡乱使用不同类型上的方法,导致出错。
所以,一般给js的变量加上一些类型注释是很有好处的。 通常我们会缺少加注释的动力,这样想吧,如果未来的某一天,你需要接手别人的项目的时候,而别人的项目里面一条注释也没有,你的想不想死。。。所以, 有因才有果,一个良好的注释真的很有用.

var found = "s";  //"string"
var abc = true;  //boolean

这应该算是一种简单的吧
还有一种是匈牙利标记法。即在每个变量名前加上一个或多个字符表示相应的类型。
通常,"o"为对象,"s"为string,"b"为Boolean,"i"表示整数,"a"表示数组等

var sName ="string"; 

但是由于看起来很丑,推荐只在函数参数里面使用就足够了。

算法的复杂度

这也是面试时候会常常问到的。

flag name description
O(1) 常数 表示不管有多少值,执行的时间都是很定的。表示简单值和存储在变量中的值
O(log n) 对数 总的执行时间和值的数量相关,但完成算法不一定获取每一个值,比如二分查找
O(n) 线性 执行时间和值的数量直接相关.ex: 遍历数组
O(n^2) 平方 总执行时间和数量有关,每个值要获取n次。ex: 插入排序

一个一个解释:

O(1)

表示你无论有多少值,获取常量值的时间都一样。

var value = 5;
var num = value + 4;
console.log(num);

上面的复杂度整体为1,因为这只是简单的变量查找。 O(1)的操作还有,数组的读取

var value = [1,2,3,4,5];
console.log(value[1]);  //2

同样也是1,不管你是多少次查找都一样。
所以说算法复杂度为O(1)的情况:只有在获取变量以及数组的时候

O(n)

最常用的是在你进行属性查找的时候。因为查找属性的时候,会遍历所有的对象上的属性,然后才返回。所以他的复杂度为O(n).

var obj = {
    name:"jimmy",
    age:19
};
console.log(obj.name);

上面访问obj.name的属性的复杂度为O(n).还有一个例子:

var name = window.location.href.substring(window.location.href.indexOf('.'));

可以数一下,上面算法的复杂度为6个O(n)因为存在了6个"."进行属性访问。这种情况下,可以优化一下,使用变量进行保存,要知道变量始终是O(1)的

var href = window.location.href;
var name = href.substring(href.indexOf('.'));

上面就缩减到了4次".",节约了33%的查询成本。 这对于大型查找是非常有好处的。但是如果你只是一次两次的查找,则使用变量和未使用变量是没有差别的。
另外对于变量的复杂度和属性的复杂度,一般来说,如果可以使用arr[xxx]形式的遍历的话,最好使用。

//jquery对象---$val
for(var i = 0;i<$val.length;i++){
    $val.eq(i).html(); //输出innerHTML
}
//优化过后
for(var i in $val){
    $val[i].innerHTML;  //输出的innerHTML
}

-----------分割线----------
这里感谢@Dreamacro 童鞋的提醒。这里特此说明一下,在V8里面引擎对于对象属性的查找其实是O(1)的操作。
(Ps:我操,你到底再说什么鬼)
其实在V8里面对于对象属性的存储同样是散列hash,但是V8就是快,为了实现向java一样的遍历速度,于是做了一点改动。在你每次新建属性的时候,V8会使用hidden class,新建一份原来对象的Copy并且添加上新属性。于是在你下次查找属性的时候他都会直接从这个新建的Class info寻找。 如果你一不小心手贱使用了delete的话,你查找的复杂度便会退化为O(n).
参考: hidden Class, V8引擎对象优化

关于O(log n)和 O(n^2),由于目前对算法不精通,还是别误导人了。

循环优化

一个常见的循环

for(var i= 0 ;i<nodelist.length;i++){
    ...
}

这个循环可以,但是不是很高效,因为每一次循环你都会去调用一次nodelist.length这个值,造成了 nodelist.length * O(n)这样一个复杂度. 优化的办法就是将length提出去

for(var i= 0,len = nodelist.length;i<len;i++){
    ...
}

上面的循环的复杂度就变为1O(1) + 1O(n); 当然上面那种简单而且更高效.
但是试想一下,如果你在循环体还需要使用nodelist[i]

for(var i= 0,len = nodelist.length;i<len;i++){
    var list = nodelist[i];
    ...
}

想一想,nodelist[i]乍看起来并不是循环体内所需要的。那我们应该怎么做呢?
很简单,利用null为false的条件自动转换

for(var i = 0,node; node = nodelist[i++];){
    node...
}
//当然你也可以直接使用
for(var node of nodelit){  //这种方式更加方便简洁,但是是es6的一个新特性
    node
    ...
}

另外循坏可以通过以下几个点来进行优化

1.减值迭代

2.简化终止条件
3.简化循环体
4.使用后测试循环--for和while都是前测试循环, do-while是后测试循环

减值迭代
: 从最大值开始减值,而不是从最小值减值迭代。

简化终止条件
: 即就是想上面例子一样,对终止条件寄存在变量当中.

简化循环体
: 这应该是一个复杂的活,简单的就是把不需要参与循环的语句提出来

使用后测试
: 这其实就是防止对空值进行循环

var len = nodelist.length,
    i = 0;
if(len>0){
    do{
        ...
    }while(++i===len);
}

这一部分可以参考一下Naraku_的读书笔记。
还有一个例子,是我在腾讯面试的时候遇到的。

从一篇全英文的文章中找到频率次数最高的单词

这里是我写的一部分代码,可以看一看
筛选单词

Duff优化

每次看的时候,都会被他的精髓刺瞎了我24K金钛合金*眼.
将循环次数展开书写
这是Andrew B.King 写出的一个比较著名的Duff装置.将do-while分成两个单独的循环。简单易懂。

var iter = Math.floor(values.length/8);
var leftover = values.length%8;
var i = 0;
if(leftover>0){
    do{
        process(values[i++]);
    }while(--leftover>0);
}
do {
    process(values[i++]); //8次处理
    process(values[i++]);
    process(values[i++]);
    process(values[i++]);
    process(values[i++]);
    process(values[i++]);
    process(values[i++]);
    process(values[i++]);
}while(--iter>0);

感觉比一般的for循环更加复杂,没错。Duff一般是用来处理比较多的循环次数的时候才会真正显示他的效率。如果你使用的循环次数就50+。那使用for效率会更高一点. 通常我们前端也用不到,因为TM这是给后端用的。

避免双重解释

这种情况一般只会发生在3种情况下, 即使用eval,Function,setTimeout里面.

eval("alert('hehe')");
var sayHi = new Function("alert('hehe')");
setTimeout("alert('hehe')",500);

以上3中会发生双重解释,原因是,原本的js解析器不能直接解析上面的string字符串,需要额外新开一个解析器来进行解释,导致的结果就是速度被拖屎了。 当然,一般的技术人员也不会手贱写这么麻烦的。
修改

alert('hehe');
var sayHi = function(){alert('hehe')}
setTimeout(function(){alert('hehe')},500);

这样写就没什么问题了。

最小化语句

即就是整合语句了。由于js是弱类型的,所以可以直接使用var或者let进行连续赋值。而且这也是JIT编译器帮你干的事.(JIT是一种能让你代码越运行越快的编译器)

//用了5条语句声明5个变量
var count = 5;
var color = 'red';
var values = [1,2,3];
var now = new Date();

//用了1条语句声明5个变量,注意每个变量用逗号隔开
var count = 5,
    color = 'red',
    values = [1,2,3],
    now = new Date();

还有就是使用数组和对象字面量
: 因为语句量减少,解析器的压力也减小了

// 创建两个对象 ----不好的方式
//one 四条语句
var values = new Array();
values[0] = 123;
values[1] = 456;
values[2] = 789;
//two 四条语句
var person = new Object();
person.name = 'jozo';
person.age = 21;
person.sayName = function(){
    alert(this.name);
};
// 创建两个对象 ----推荐的方式
//one 1条语句
var values = [123,456,789]
//two 1条语句
var person = {
    name : 'jozo',
    age : 21,
    sayName : function(){
    alert(this.name);
};

总的来说, js优化之路漫漫,没有年年月月的积累,是不可能达到什么特别高的level的。所以说,活到老学到老,这是永恒的真理~~


villainhr
7.8k 声望2.2k 粉丝