美文网首页
JS语言精粹

JS语言精粹

作者: tency小七 | 来源:发表于2019-06-08 17:07 被阅读0次

    小小知识点:

    1. typeof是一个操作符,并不是function。所以typeof后面不用加括号。
      参见MDN


      image.png
    2. 关于 typeof null // Object
      null 值表示一个空对象指针,而这也正是使用 typeof 操作符检测 null 值时会返回"object"的原因,如

    3. 只声明不赋值,则默认值为undefined

    4. 关于const

       const num = 1;
       num = 2; //Uncaught TypeError: Assignment to constant variable.
      
       const obj = {
         data: 1
       }
       obj.data = 2;//没有报错
      

      咦,不是说const定义的常量不能被改变吗?
      const仅保证指针不发生改变,修改对象的属性不会改变对象的指针,除非我们直接重写对象也就是说const定义的引用类型只要指针不发生改变,其他的不论如何改变都是允许的。所以

       const obj = {
           data:1
       }
      
       obj = {
           data:4
       }//Uncaught TypeError: Assignment to constant variable.
      
    5. 关于arguments,我们更推荐这么使用
      这时的args就是真正的数组了。

       function foo(...args){
           return args;
       }
       foo(1,2,3)//[1,2,3]
      
    6. 函数的返回值有两种结果:
      显示调用return 返回 return 后表达式的求值
      没有调用return 返回 undefined

    7. 关于this
      在普通函数中
      严格模式下,this指向undefined
      非严格模式下,this指向全局对象(Node中的global,浏览器中的window)
      在ES6中默认使用严格模式,所以在普通函数中你会发现this指向undefined。

    8. 语句和表达式
      JavaScript是一种语句优先的语言
      逗号运算符的左右两侧都必须是表达式!如果是a:1,那肯定会报错。

    逗号表达式的一般形式是:表达式1,表达式2,表达式3……表达式n
    逗号表达式的求解过程是:先计算表达式1的值,再计算表达式2的值,……一直计算到表达式n的值。最后整个逗号表达式的值是表达式n的值。
    看下面几个例子:
    x=8*2,x*4 /整个表达式的值为64,x的值为16/
    (x=8*2,x*4),x2 /整个表达式的值为128,x的值为16/
    x=(z=5,5*2) /
    整个表达式为赋值表达式,它的值为10,z的值为5/
    x=z=5,5*2 /
    整个表达式为逗号表达式,它的值为10,x和z的值都为5*/
    逗号表达式用的地方不太多,一般情况是在给循环变量赋初值时才用得到。所以程序中并不是所有的逗号都要看成逗号运算符,尤其是在函数调用时,各个参数是用逗号隔开的,这时逗号就不是逗号运算符。

    有时候会遇到一些很奇怪的错误,就尽量往语句优先这个方向想。

    1. 立即执行函数

      function(){}() //Uncaught SyntaxError: Unexpected token (
      这里为什么会报错呢?如果不加第一对括号,无论是

       function(){
          /* 代码 */
       }();
      

      还是

       function(){
           /* 代码 */
       }
      

      都是会报错的。因为js的引擎会把这里的 function 看成是函数声明,而函数声明不允许没有函数名,因此会对匿名函数报错。

      匿名函数只允许以表达式的形式存在,例如:

       setTimeout(function(){
           /* 代码 */
       }, 1000);
      

      这里的匿名函数就是作为 setTimeout 的一个参数,是表达式,这种写法是允许的。

      或者:

       var foo = function(){
           /* 代码 */
       };
      

      也就是说我们稍微修改一下,var fn = function(){}(),也不会报错。
      这是为什么?
      因为这是把一个匿名函数赋值给一个变量,匿名函数在该语句中充当函数表达式的角色。

      如果这里的函数有名字呢?不会报错,但语义会发生变化。例如:

       function foo(x){
           /* 代码 */
       }(1);
      

      其实这里的代码就相当于

       function foo(x){
           /* 代码 */
       };
       (1);
      

      原因是js引擎会认为前面的函数是一个函数声明的语句,而后面的(1)是另一个单独的语句,于是执行后面的语句,在控制台输出1。

      js的括号有几种不同的作用,其中一种作用就是:表示在括号内的是表达式而不是语句。具体到这个例子上,第一对括号就是告诉js引擎,这里面的匿名函数是一个函数表达式,而不是函数声明语句。因此加了这个括号之后,就不会报错了。
      (function(){})() //强制其理解为函数,()里面放表达式,“函数()”表示执行该函数,即声明后立即执行了。

    2. 补充原型链的一个小知识:
      每个原型对象prototype中都有一个constructor属性,默认指向函数本身。

    3. 闭包和内存泄露的问题
      首先应当明确的是内存泄漏可能是代码的问题,而不是闭包的问题,如果为了避免内存泄漏而不去用闭包的话,有些问题是很难解决的。

    在IE9之前,BOM和DOM对象都是使用C++以COM对象的形式来实现的,COM对象的垃圾收集机制采用的就是计数策略,所以IE中只要涉及到BOM和DOM对象,就很可能会存在循环引用的问题。

    闭包实际上很容易造成JavaScript对象和DOM对象的隐蔽循环引用。也就是说,只要闭包的作用域链中保存着一个HTML元素,那么就意味着这个元素将无法被销毁。例如下面的:

      function assignHandler(){
        var element = document.getElementById("someElement");
        element.onclick = function(){
            alert (element.id);
       }; 
    }
    

    注意,闭包的概念是一个函数的返回值是另外一个函数,返回的那个函数如果调用了其父函数内部的其他值,这是一个element元素事件处理的一个闭包。这个闭包创建了一个循环引用。

    1. JavaScript对象element引用了一个DOM对象(其id为“someElement”); JS(element) ----> DOM(someElemet)
    2. 该DOM对象的onclick属性引用了匿名函数闭包,而闭包可以引用外部函数assignHandler 的整个活动对象,包括element ; DOM(someElement.onclick) ---->JS(element)
      匿名函数一直保存着对assginHandler()活动对象的引用,它所占的内存永远不会被回收。

    可以对代码稍稍改进:

      function assignHandler(){
        var element = document.getElementById("someElement");
        var id = element.id;
        element.onclick = function(){
            alert (id);
       }; 
       element = null;
    }
    

    可以首先通过引用一个副本变量消除循环引用,但是这个闭包包含外部函数的全部活动对象,所以就算不直接引用,匿名函数一直都包含着对element的引用。所以最后需要手动设置element变量为null.

    闭包的很大作用是可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。
    只要变量被任何一个闭包使用了,就会被添到词法环境中,被该作用域下所有闭包共享。

    绝对不能因为内存泄漏而不使用闭包。

    JS中只要越过了闭包,作用域,原型这几座大山,对JS的理解就高了一个层次了!
    作用域的文章可以查看下面的网址!!!https://lemontency.github.io/2019/03/20/%E5%8E%9F%E5%9E%8B%E4%B8%8E%E5%8E%9F%E5%9E%8B%E9%93%BE/

    最后来几道作用域和this的题吧!

    if(!(username in window)){
        var username = 'zty';
    }
    console.log(username) //undefined
    

    怎么理解呢。首先在ES5的代码中并没有块级作用域的概念,var username直接变量提升到最顶部,此时全局作用域中有username并且值为undefined,所以!(username in window)为false,跳过赋值部分,username依旧是undefined。

    //差点就错了
    //执行顺序是
    //声明函数foo,注意是整个函数一起提升上去的
    //调用函数foo
    //声明变量test
    //console.log(test)
    //test = "bbb"
    //console.log(test)
    var test = "aaa";
    function foo(){
        console.log(test);
        var test = "bbb";
        console.log(test);
    }
    foo(); //undefined bbb 
    console.log(test)//aaa
    

    也就是说上面的代码的执行顺序其实是这样的:

    var test = aaa;
    function foo(){
        var test;
        console.log(test);
        var test = "bbb";
        console.log(test);
    }
    foo();
    console.log(test);
    
    var name = 'global';
    function A(name){
        //传参的优先级要高于后面的声明
        //虽然后面有变量提升,但是是没有影响的
        alert(name);//3
        this.name = name;
        var name = '1';
    }
    A.prototype.name = '2';
    var a = new A('3');
    alert(a.name);//3
    delete a.name;//删除对象的属性但是不删除对象原型链上的属性
    alert(a.name);//2
    

    比较好的实践就是把要用到的变量声明都写到函数最前面。
    再来看一道容易做的题:

    function fun(n,o){
        console.log(o);
        return {
            fun:function(m){
                return fun(m,n)
            }
        }
    }
    var a = fun(0);//undefined
    a.fun(1);
    a.fun(2);
    var b = fun(0).fun(1).fun(2).fun(3);
    var c = fun(0).fun(1);
    c.fun(2);c.fun(3);
    

    我们看到:return返回的对象的fun属性对应一个新建的函数对象,这个函数对象将形成一个闭包作用域,使其能够访问外层函数的变量n及外层函数fun,为了不让fun属性和fun对象混淆,先更改一下代码:

    function _fun_(n,o){
        console.log(o);
        return {
            fun:function(m){
                return _fun_(m,n)
            }
        }
    }
    var a = _fun_(0);//undefined
    a.fun(1);//
    a.fun(2);
    var b = _fun_(0).fun(1).fun(2).fun(3);
    var c = _fun_(0).fun(1);
    c.fun(2);c.fun(3);
    

    这道题难度挺大,但是一定要做。
    讲师小tips:每次调用fun就相对应的在纸上还原作用域链。

    _fun_函数执行,因为第2个参数未定义,输出undefined。然后返回一个对象,带有fun属性,指向一个函数对象,带有闭包,能够访问到_fun_和变量n。
    a.fun(1)执行返回的对象的fun方法,传入m的值1,调用返回_fun_(1,0),打印出0;
    a.fun(1)执行返回的对象的fun方法,传入m的值1,调用返回_fun_(2,0),打印出0;

    接下来的var b = _fun_(0).fun(1).fun(2).fun(3);可以等价为

    var b = _fun_(0);
    var b1 = b.fun(1);
    var b2 = b1.fun(2);
    var b3 = b2.fun(3);
    前两句的和我们刚刚分析的情况是一样的,var b = _fun_(0);首先返回一个对象,带有fun属性,指向一个函数对象,带有闭包,能够访问_fun_和变量n。
    b.fun(1)传入m的值为1,调用返回_fun_(1,0),打印出0;
    b1.fun(2)传入m的值为2,调用返回_fun_(2,1),打印出1;
    b2.fun(3)传入m的值为3,调用返回_fun_(3,2),打印出2;

    接下来var c = _fun_(0).fun(1);情况和前面的是一样的,先打印出undefined,再打印出0;
    c.fun(2)传入m的值为2,调用返回_fun_(2,1),打印出1;
    c.fun(3)传入m的值为,调用返回_fun_(3,1),打印出1;

    还要注意,这里传递的是一个简单的数据类型,而不是引用的数据类型。
    看看这个?
    https://lemontency.github.io/2019/03/20/JS%E4%B8%AD%E7%9A%84%E6%8C%89%E5%80%BC%E4%BC%A0%E9%80%92%E5%92%8C%E6%8C%89%E5%BC%95%E7%94%A8%E9%97%AE%E9%A2%98-1/

    参考:https://www.zhihu.com/question/48238548
    https://segmentfault.com/a/1190000004187681

    相关文章

      网友评论

          本文标题:JS语言精粹

          本文链接:https://www.haomeiwen.com/subject/toxqjqtx.html