JavaScript小总结

最近在整理JavaScript的知识,确切的说是在梳理ECMAScript制定的标准,写到笔记里以备回顾。
山高人为峰,有龙则灵。—— http://www.cnblogs.com/litaiqing  (此文原地址) 2016-09-06 【大学毕业第1个半月总结JavaScript记】
 
1.闭包(closure
这个特性无非是一个热门内容,去搜一下“JavaScript闭包”,会发现一万个人一万个理解,当然理解的深浅也是不一样,错误也不少。
几乎每个JavaScript面试官都会问到这个问题,通常会让你先说出闭包定义,然后写个闭包来说明。
总结无非是把接受的信号按照自己的设定格式化,所以下面就假设我被面试了。)
 
a.闭包是什么:
    指的是词法表示包括不被计算的变量的函数,也就是说,函数可以使用函数之外定义的变量。(这句话来自w3school)
    意味着当前作用域总是能够访问外部作用域中的变量。
    谁让现在面试的套路那么多呢,如果那个面试官也是个一知半解,按照上述回答,或许会认为你背下了概念吧。+ _ +~
    所以:
        四个字形容闭包,吃里扒外
        曾经看到一篇国外的文章问到闭包时有下面这句话:“如果你不能向一个六岁的孩子解释清楚,那么其实你自己根本就没弄懂。”
       我想六岁的孩子也会理解吃里扒外吧。
       (还有什么比中国成语更好的语言吗?所以在汉语圈天天秀英语,脑袋会退化。这种人算不算个吃里扒外的闭包呢...)
        因为 函数 是 JavaScript 中唯一拥有自身作用域的结构,因此闭包的创建依赖于函数。 (所以有下面的 b)
 
b.写个闭包
    一般都会去这样写:
1 function a() { // 不要对我起名字这件事耿耿于怀,毕竟起名字要比吹牛难得多
2     var i = 0;
3     function b(){
4        return ++i;
5     };
6     return b();
7 };
 或者写个匿名的:
   这种匿名函数有个专词叫 匿名包裹器 ,这种自执行匿名函数会获得变量的一个拷贝,所以如果把匿名包裹器去掉,你会得不到想要的结果。
1 for(var i = 0; i < 10; i++) { // 来自JavaScript神秘花园,实在想不出比这行代码更能体现闭包作用的代码了~
2     (function(e) { // 匿名包裹器
3         setTimeout(function() {
4             console.log(e);  
5         }, 1000);
6     })(i);
7 };

 但是上面的代码是谈闭包就会想起来吧,因为面试官的心理准备就是这些东西了。要攻其不备,于是:

1 var name = 'litaiqing';
2 function(){
3    console.log(name);
4 };
    这就是个闭包,是的,你没有看错, 这是 ECMAScript 中使用全局变量是一个简单的闭包。
    让我们再看看闭包的定义:函数可以使用函数之外定义的变量。显而易见上述代码是个闭包。
    这行简单的代码就诠释了闭包的基本定义,当前作用域总是能够访问外部作用域中的变量,
    然后和面试官谈及闭包的一些作用,比如模拟私有变量,避免引用错误等,此时再谈及最上面两个代码,一定会让面试官深信你真懂得闭包。
    也就是说,闭包不光能访问自身作用域的变量(吃里),而且还可以使用自身作用域之外的变量(扒外)。

 

2.var
    曾经看过一个培训机构的视频,里面说到JavaScript是弱类型语言,所以声明变量可以不用var
   (此刻他正在一个函数中写(私有)变量而且没有var,或许是只是想表达可以不写var吧。)
    那么var写于不写有什么区别呢?
    var的作用无非是声明变量。但是要注意ECMAScript 的解释程序遇到未声明过的标识符时,用该变量名创建一个全局变量,并将其初始化为指定的值。
    所以在函数内部去定义一个私有变量,一定要加上var,这也是体现专业的一点。除非确实需要在外部引用内部修改的全局变量。
    例子:(运行一下,不就什么都明白了吗~自己动手,丰衣足食。)
 1 var a = 0;
 2 (function(){
 3    a = 1; // 不加var,提升为全局变量
 4 })();
 5 console.log(a); // 1
 6 // -----------------------------------
 7 (function(){
 8    var b = 1;
 9 })();
10 console.log(typeof b); // undefined
11 console.log(b); // b is not defined

 函数内不使用 var 关键字声明变量将会覆盖外部的同名变量。 所以不写var是个要格外谨慎的动作。

 

3.变量声明提升(Hoisting)
   在第2点中说到的声明变量用var是不是很有意义呢,但是变量的声明还会有提升的规则。不多说先来段代码:
1 // 来自 Nettuts+ 的一段代码,生动的阐述了 JavaScript 中变量声明提升规则
2 var myvar = 'my value'; 
3 (function() {  
4     alert(myvar); // undefined  
5     var myvar = 'local value';  
6 })();  
为什么myvar会是undefined呢?这是因为JavaScript 会提升变量声明。这意味着 var 表达式和 function 声明都将会被提升到当前作用域的顶部。
  上述代码在运行时,会变成如下:
1 // 来自 Nettuts+ 的一段代码,生动的阐述了 JavaScript 中变量声明提升规则
2 var myvar = 'my value'; 
3 (function() {
4     var myvar; // 移动到当前作用域的顶部,此时myvar显然为undefinded.
5     alert(myvar); // undefined  JavaScript会默认先从当前作用域查找变量
6     myvar = 'local value';  
7 })();  
 
4.作用域与命名空间
  作用域是什么?作用域是指变量的适用范围。
  ECMAScript 只有公用作用域
  对 ECMAScript 讨论公用和私有作用域几乎毫无意义,因为 ECMAScript 中只存在一种作用域 - 公用作用域。
  ECMAScript 中的所有对象的所有属性和方法都是公用的。
  因此,定义自己的类和对象时,必须格外小心。记住,所有属性和方法默认都是公用的!同时严格来说,ECMAScript 并没有静态作用域。(w3school)
  那么我们如何去写一个作用域呢?
  JavaScript 支持一对花括号创建的代码段,但是并不支持块级作用域; 而仅仅支持函数作用域。 
function test() { // 一个作用域 (来自JavaScript秘密花园)
    for(var i = 0; i < 10; i++) { // 不是一个作用域
        // count
    };
    console.log(i); // 10 // i在test这个函数作用域里
};
JavaScript 中没有显式的命名空间定义,这就意味着所有对象都定义在一个全局共享的命名空间下面。(上述ECMAScript的特性。)
  这样就有了以下的问题(称为问题不太确切,不过也确实引起了问题)
  每次引用一个变量,JavaScript 会向上遍历整个作用域直到找到这个变量为止。 如果到达全局作用域但是这个变量仍未找到,则会抛出 ReferenceError 异常。
 (记住这点,因为第5点要谈论For In!~)
 
 
5.for in 循环,你喜欢用吗
   首先将第4点最后一句的话拿下来:
   每次引用一个变量,JavaScript 会向上遍历整个作用域直到找到这个变量为止。 如果到达全局作用域但是这个变量仍未找到,则会抛出 ReferenceError 异常。
  这句话就是为什么不用for in的原因。
 
1 // 修改 Object.prototype (来自JavaScript秘密花园~)
2 Object.prototype.bar = 1;
3  
4 var foo = {moo: 2};
5 for(var i in foo) {
6     console.log(i); // 输出两个属性:bar 和 moo // 不信运行一下咯~
7 };
上述代码中,我们仅仅是打印foo的属性moo,为什么会出现属性bar呢?
 JavaScript 是唯一一个被广泛使用的基于原型继承的语言。显然foo继承了Object的原型上的属性和对象。
当查找一个对象的属性时,JavaScript 会向上遍历原型链,直到找到给定名称的属性为止。
到查找到达原型链的顶部 - 也就是 Object.prototype - 但是仍然没有找到指定的属性,就会返回 undefined。
>>>>>>  强行插入:
         在 JavaScript 核心语言中,全局对象的预定义属性都是不可枚举的,所有可以用 for/in 循环列出所有隐式或显式声明的全局变量。
1 for (var k in this) { // 运行一下咯~
2     console.log(k);
3 };
<<<<<
所以在这个for-in循环中,in操作会向上遍历属性,于是bar 也输出了。
那么如果仅仅想输出foo自身的属性(确切的说是{moo:2}这个对象)呢?
于是,我们就要去说明一下hasOwnProperty 函数。
hasOwnProperty 是 JavaScript 中唯一一个处理属性但是不查找原型链的函数。
为了判断一个对象是否包含自定义属性而不是原型链上的属性, 我们需要使用继承自 Object.prototype 的 hasOwnProperty 方法。
当检查对象上某个属性是否存在时,hasOwnProperty 是唯一可用的方法。
于是上面的for-in解决办法为:
1 // 修改 Object.prototype (来自JavaScript秘密花园~)
2 Object.prototype.bar = 1;
3  
4 var foo = {moo: 2};
5 for(var i in foo) {
6     if(foo.hasOwnProperty(i)){
7        console.log(i); // 输出一个属性:moo
8     };
9 };
由于 for in 总是要遍历整个原型链,因此如果一个对象的继承层次太深的话会影响性能。
另一注意点:
JavaScript 不会保护 hasOwnProperty 被非法占用,因此如果一个对象碰巧存在这个属性, 就需要使用外部的 hasOwnProperty 函数来获取正确的结果。
 1 var foo = { // (来自JavaScript秘密花园)
 2     hasOwnProperty: function() {
 3         return false;
 4     },
 5     bar: 'Here be dragons'
 6 };
 7  
 8 foo.hasOwnProperty('bar'); // 总是返回 false
 9  
10 // 使用其它对象的 hasOwnProperty,并将其上下为设置为foo
11 {}.hasOwnProperty.call(foo, 'bar'); // true
如果要遍历的对象是个数组,一定要先缓存这个数组的长度,然后再用普通for-length遍历!否则数组越大,性能越差。
由于 for in 循环会枚举原型链上的所有属性,唯一过滤这些属性的方式是使用 hasOwnProperty 函数, 因此会比普通的 for 循环慢上好多倍。
 1 Object.prototype.bar = 1; // 原型继承的双刃剑~~
 2 var list = [1, 2, 3, 4, 5, ...... 100000000];
 3 // 性能优,而且不会出现bar属性
 4 for(var i = 0, l = list.length; i < l; i++) {
 5     console.log(list[i]);
 6 };
 7 // 性能差,会出现bar属性
 8 for(var k in list) {
 9     console.log(k);
10 };

 

 

6. "==" PK "==="
使用 == 被广泛认为是不好编程习惯。
JavaScript 是弱类型语言,这就意味着,等于操作符会为了比较两个值而进行强制类型转换。
所以,在JavaScript里所有的对象比较最后都会强制类型转换为数值,然后进行比较。所以会带来性能消耗!!!
来一个最经典的例子好了。
1 '' == '0'; // false
2 '' == 0; // true
3 '0' == 0; // true
== 的比较过程遵循以下原则:
执行类型转换的规则如下:
1. 如果一个运算数是 Boolean 值,在检查相等性之前,把它转换成数字值。false 转换成 0,true 为 1,(这点和C语言很像吧)。
2. 如果一个运算数是字符串,另一个是数字,在检查相等性之前,要尝试把字符串转换成数字。
3. 如果一个运算数是对象,另一个是字符串,在检查相等性之前,要尝试把对象转换成字符串。
4. 如果一个运算数是对象,另一个是数字,在检查相等性之前,要尝试把对象转换成数字。
5. 如果两个运算数都是字符串,则会逐个比较字符串中每个字符的ASCII码。(这是我自己加的,上面4条来自w3school,加上这条会更好!)
(两个运算数使用==比较,只要有一个是数字或者Boolean,就会将两个运算数都会尝试转换为数字!如果无法转换就会报错,例如:{} == 0)
在比较时,该运算符还遵守下列规则:
6. 值 null 和 undefined 相等。
7. 在检查相等性时,不能把 null 和 undefined 转换成其他值。
8. 如果某个运算数是 NaN,等号将返回 false,非等号将返回 true。
9. 如果两个运算数都是对象,那么比较的是它们的引用值。如果两个运算数指向同一对象,那么等号返回 true,否则两个运算数不等。
重要提示:即使两个数都是 NaN,等号仍然返回 false,因为根据规则,NaN 不等于 NaN
所以:
 1 '' == '0'; // false
 2 /*
 3    1. '' == '0' 都是字符串,转换为转成ASCII码
 4    2. 显而易见,''、'0' ASCII码是不相等的('' < '0' // true )。 返回false
 5  */
 6 0  == '';  // true
 7 /*
 8    1. 0 == '' 符合第2条规则,于是先把字符串转为数字。
 9    2. Number('') 会转化为 0 
10    3. 于是 0 == 0 返回true
11  */
12 '0' == 0; // true
13 /*
14    1. '0' == 0 符合第2条规则,于是先把字符串转为数字。
15  */
现在要说一下 ‘==’ PK ‘===’ 的事了。
与==不同的是===不会进行类型转换,所以也不会有多余的性能消耗。
1 ''  ===  '0'; // false
2 ''  ===   0 ; // false
3 '0' ===   0 ; // false
上述结果没什么好讨论的。
严格等于操作符要注意的一点是 当其中有一个操作数为对象时,只有对象的同一个实例才被认为是相等的。
所以 使用===要比==更好一些,如果类型需要转换,应该在比较之前显式的转换, 而不是使用语言本身复杂的强制转换规则。
 
7.  “ ; ” 的力量
    有没有注意到,上面写的代码中,所有该结束的地方都加上了“;”。
    有人说JavaScript可以不写分号结束,换行就可以,写起来还舒服。
    此人说的正确与否不必讨论,那么JavaScript到底需不需要分号呢,答案是 需要 。
    这是因为JavaScript解析器它需要分号来就解析源代码。
    JavaScript 解析器在遇到由于缺少分号导致的解析错误时,会自动在源代码中插入分号。
    注意!在前置括号的情况下,解析器不会自动插入分号。
    所以,如果不写分号,就会有以下情况:
1 var a = function(){
2     console.log('run') // 没有分号
3 }
4 a() // 没有分号
5 alert('testing!') // 没有分号
6 ([]).forEach(function(i) {}) // 没有分号

那么对于上述这些代码,JavaScript在解析时需要自己判断需要在哪些地方插入分号。

1 var a = function(){
2     console.log('run'); // 加上分号
3 };
4 a(); // 加上分号
5 // 下面两个代码由于([])是前置括号的情况,所以两句代码中间解析器不会自动插入分号!!!
6 alert('testing!')([]).forEach(function(i) {}); // 加上分号
解析器显著改变了上面代码的行为,在另外一些情况下也会做出错误的处理。
运行此代码发现,在alert()函数运行结束后,会抛出 alert(...) is not a function(…) 这样的错误!
同时:JavaScript 不能正确的处理 return 表达式紧跟换行符的情况, 虽然这不能算是自动分号插入的错误,但这确实是一种不希望的副作用。
1 function add(a, b){
2     return 
3        a + b; // 注意此处与return不在同一行。
4 };
5 console.log(add(1, 2)); // undefined
 自动分号插入被认为是 JavaScript 语言最大的设计缺陷之一,因为它能改变代码的行为。
    所以我们要在每个需要分号的地方都要加上分号!(这样最好。)
 
8.JavaScript高手代码举例(这里说技巧代码比较合理,但是谁让我吹起来了呢~)
    除了上面7点的简略总结,还有很多JavaScript重要的高级特性比如 typeof(或许是 JavaScript 中最大的设计缺陷)、this实质、 继承等知识点就不写了,因为我迫不及待要谈代码了。
    想过没有菜鸟和高手到底差在哪里?
    基础?经验?学历?... 
    菜鸟和高手唯一的差别是思想,也就是动不动脑筋,任何人做事只要会动脑筋就可以成为高手。
   
   a. 位运算~ 
    场景:
            如果字符串‘abc’中含有‘a’,就弹出yes,否则弹出no。
    普通代码一般会这样:
1 if('abc'.indexOf('a') > -1){
2    alert('yes');
3 } else {
4    alert('no');
5 };
不过 > -1 太难敲了,难受,浑身难受!!
 那么就干了这碗大力吧!
1 ~'abc'.indexOf('a')?alert('yes'):alert('no');
  介绍位运算 NOT 过程:
       位运算 NOT 是三步的处理过程:(实际上就是 对数字求负,然后减 1)
        1.把运算数转换成 32 位数字
         2.把二进制数转换成它的二进制反码
         3.把二进制数转换成浮点数
    我们知道 'abc'.indexOf('a')  如果包含‘a’(显然是包含),就会返回一个大于-1的值(也就是起始位置角标),否则返回-1
     那么如果一个大于-1的数经过一次NOT运算会变成什么呢?
     (希望我下面的数学表达式没错吧,毕竟从小就数学不太好,数学符号都忘得差不多了~)
     令 x = {x | x > -1,且x ∈ N},  则有 -x-1 > 0 。
    所以只要是包含指定的字符串~'abc'.indexOf('a') 就会返回一个大于 0 的正整数。
    那么当x = -1时也就是不包含制定字符串时,就会返回 0。
    回到JavaScript中来,对于Number类型的ToBoolean会有以下规则:
    Number    如果参数为 +0, -0 或 NaN,则结果为 false;否则为 true。
    从而,~'abc'.indexOf('a') 在if 中 的逻辑值就是如果包含字符串就为true,否则为false。
    不过更专业一点写法是这样:
1 !!~'abc'.indexOf('a')?alert('yes'):alert('no');

 加两个叹号就专业了?是的,这种做法把数值类型转换为了boolean类型,毕竟JavaScript的隐式转换比较怕怕~~(看第6点)。

 

  b. 逻辑运算符 && ||
    && 有如下特性规则:
              如果第一个运算数决定了结果,就不再计算第二个运算数。
    如果某个运算数不是原始的 Boolean 型值,逻辑 AND 运算并不一定返回 Boolean 值:
如果一个运算数是对象,另一个是 Boolean 值,返回该对象。
如果两个运算数都是对象,返回第二个对象。
如果某个运算数是 null,返回 null。
如果某个运算数是 NaN,返回 NaN。
如果某个运算数是 undefined,发生错误。
    于是: 返回最后一个对象,如果没有对象就返回boolean值。

     || 有如下特性规则:

            如果第一个运算数值为 true,就不再计算第二个运算数。
            如果某个运算数不是 Boolean 值,逻辑 OR 运算并不一定返回 Boolean 值:
如果一个运算数是对象,并且该对象左边的运算数值均为 false,则返回该对象。
如果两个运算数都是对象,返回第一个对象。
如果最后一个运算数是 null,并且其他运算数值均为 false,则返回 null。
如果最后一个运算数是 NaN,并且其他运算数值均为 false,则返回 NaN。
如果某个运算数是 undefined,发生错误。
    于是:返回第一个对象,如果没有对象就返回boolean值。
    从而:
1 if(~'abc'.indexOf('a')){
2    alert('yes');
3 }
4 // 简写为
5 !~'abc'.indexOf('a') || alert('yes');
6 // 或者
7 ~'abc'.indexOf('a') && alert('yes');
  
 c. if -else & switch简写
现在有“菜鸟”、“一般”、“专业”、“高手”,码农F4组合各显身手:
     场景:
            现在给不同年龄的人发放奖金:
                    如果年龄为 22 岁,则发放2200RMB,
                    如果年龄为 32 岁,则发放3200RMB,
                    如果年龄为 42 岁,则发放4200RMB,
                    如果年龄为 52 岁,则发放5200RMB
菜鸟代码会这样写:
 1 var age = 22;
 2 var money = 0;
 3 if(age === 22){
 4    money = 2200;
 5 } else if(age === 32){
 6    money = 3200;
 7 } else if(age === 42){
 8    money = 4200;
 9 } else if(age === 52){
10    money = 5200;
11 };
12 console.log('age:'+age+' - money:'+money);

 一般代码会这样写:

 1 var age = 22;
 2 var money = 0;
 3 switch(age){
 4   case 22:
 5     money = 2200;
 6     break;
 7   case 32:
 8     money = 3200;
 9     break;
10   case 42:
11     money = 4200;
12     break;
13   case 52:
14     money = 5200;
15     break;
16 }
17 console.log('age:'+age+' - money:'+money);

专业代码会这样写:

1 var age = 22;
2 var money = (age === 22 && 2200) || 
3             (age === 32 && 3200) || 
4             (age === 42 && 4200) || 
5             (age === 52 && 5200) || 
6             0;
7 console.log('age:'+age+' - money:'+money);

高手代码会这样写:

1 var age = 22;
2 var money =  {'22':2200,'32':3200,'42':4200,'52':5200}[age]|| 0;
3 console.log('age:'+age+' - money:'+money);
现在变需求了!
    新场景:
            现在给不同年龄段的人发放奖金:
                    如果年龄大于 22 岁,则发放2200RMB,
                    如果年龄大于 32 岁,则发放3200RMB,
                    如果年龄大于 42 岁,则发放4200RMB,
                    如果年龄大于 52 岁,则发放5200RMB
            重叠区域按最近区域处理。
  菜鸟这样改写:
 1 var age = 22;
 2 var money = 0;
 3 if(age >= 22){
 4    money = 2200;
 5 } else if(age >= 32){
 6    money = 3200;
 7 } else if(age >= 42){
 8    money = 4200;
 9 } else if(age >= 52){
10    money = 5200;
11 };
12 console.log('age:'+age+' - money:'+money);

 一般这样改写:

 1 var age = 22;
 2 var money = 0;
 3 switch(Math.floor(age/10)){
 4   case 2:
 5     money = 2200;
 6     break;
 7   case 3:
 8     money = 3200;
 9     break;
10   case 4:
11     money = 4200;
12     break;
13   case 5:
14     money = 5200;
15     break;
16 }
17 console.log('age:'+age+' - money:'+money);

专业这样改写:

1 var age = 22;
2 var money = (age >= 22 && 2200) || 
3             (age >= 32 && 3200) || 
4             (age >= 42 && 4200) || 
5             (age >= 52 && 5200) || 
6             0;
7 console.log('age:'+age+' - money:'+money);

高手这样改写:

1 var age = 22;
2 var money =  {'2':2200,'3':3200,'4':4200,'5':5200}[Math.floor(age/10)] || 0;
3 console.log('age:'+age+' - money:'+money);

如果让你改写,你会改写成什么样呢?

 

  d. 玩转JavaScript语法

1 // 99 转成字符串
2 // 一般:
3 99 + '';
4 // 高手:
5 99..toString();//是的你没有看错,是两个点。这也是JavaScript解析器的一个缺陷
1 (function(e){
2    alert(e);
3 })(123456);// ()()玩烂了吧
4 +function(e){
5    alert(e);
6 }(123456);// 这种是不是有点新鲜感
 
 
时间不多了, 就写到这里吧,好多特性没写出来。2016-09-06 18:26。晚上加班...求解脱。
http://www.cnblogs.com/litaiqing

 

 
 
 
 
posted @ 2016-09-06 18:30  登高一呼  阅读(287)  评论(0编辑  收藏  举报