zoukankan      html  css  js  c++  java
  • 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
     
     
     
     
  • 相关阅读:
    返回图片宽高比
    3.1/3.2图片上传类
    php获取图片的拍摄及其他数据信息
    上传类
    pathinfo()的用法
    上传并压缩图片
    将数组转化为键值对
    css3判断某个li标签
    禁止滚动条/启用滚动条
    Keepalived + haproxy双机高可用方案
  • 原文地址:https://www.cnblogs.com/litaiqing/p/3d3329405ace8bc0ea4313d4daf1ae1d.html
Copyright © 2011-2022 走看看