诡异的JS知识

456 阅读5分钟

这是我参与8月更文挑战的第7天,活动详情查看:8月更文挑战

前言

在日常开发和学习中,我总能遇到一些奇怪的问题,所以记录了下来!

基本语法和变量

连续定义变量

在日常开发中,我们经常会使用到定义多个变量的场景

   var a=b=3

但是这到了JS解释器眼里就会变成

   b=3
   var a=b

因为 =运算符是自右向左运算的,所以会先执行b=3,再执行var a=b

眨眼看去也没什么错呀,也只是在定义b时未使用var,熟悉全局作用域的人应该知道在JS中未用varletcount定义变量,那么变量默认是定义到全局的。

来看看例子:

   function test(){
       var a=b=2
       console.log(a)  //2
       console.log(b)  //2
   }
   test()
   console.log(a)  // ReferenceError: a is not defined
   console.log("a" in window)  //false
   
   console.log(b)  // 2
   console.log("b" in window)  //true

明显看到,b定义到了全局,这样就会造成全局污染了,而且这种错是很难找的。如果要定义多个变量可以,隔开。

   var a = 2,b = a;   //正确写法

这要注意,号运算符是自左向右运算的,先从左边开始定义变量的,先执行var a = 2,再执行var b=a。JS解释器如下:

   var a = 2;
   var b = a; 
常见的诡异比较和运算
   null >= 0 //true
   null <= 0 //true

结合上面两个表达式,我们可以推出null==0,但是放到控制台上面才发现不是这样的结果

   null == null  //false

同样是比较运算符,为什么推理出来结果不同?

这是因为JS==运算符的隐式转换和>=<=是不一样,所以才会出现后面的效果,所以有的时候不能用数学的方式去思考代码

   [] == []  //false
   [] == ![] //false

上面这样的题在面试中可以说是层出不穷的,因为刁钻而且不容易理解,但是我们正要克服这些(隐式转换的知识点)

逗号运算符

,运算符是我们最常用的,也是最容易被忽略的,因为不会有人把,运算符的语句直接参与运算,比如:

  let a = (1,2,3)  // a=3
  (1,2) + (6,2)    // 4

虽然这样的语句不常见,但是返回结果的原因还是要知道的。

其实也很简单,逗号表达式的值就是最后一个逗号后面的值。比如:(1,2)表达式,那么它的值就是2

逗号运算符表达式一般会出现的压缩源码和面试中

基本类型的包装类

什么是包装类我就不讲了,直接看看下面的例子

  var str="abc"
  console.log(str.length)  //3
  str.length = 6
  console.log(str.length)  //3

发现了什么,明明修改了strlength属性,再次访问却还是原来的值!!!

我们可以将语句拆开来看,其实JS解释器会进行的是如下操作

  var str="abc"
  console.log(new String(str).length)  //3
  new String(str).length = 6
  console.log(new String(str).length)  //3

这样是不是就很容易就可以看出问题了喃,因为每次访问length属性都是新定义一个对象,所以你就算修改了对象的length属性,下次访问还是访问的一个新的对象,所以length

函数

自执行函数常见考点
   function test(){
       console.log(1)
   }()  //SyntaxError: Unexpected token ')'

上面书写自执行函数的方式肯定会报错,这里我也就不解释了(知识点:函数表达式和函数声明的区别)。

那再来看看如下的方式会不会报错喃

   function(){
       console.log(1)
   }(1)      //1

可以看到只是添加了一个函数形参,却不会产生报错了。老规矩看看JS解释器如何解释这串代码的吧

   function(){
       console.log(1)
   }
   (1)

这样是不是就很清晰了呀,因为()运算符的很高,并且里面还有数据,所以就没有把()当作函数执行符号了

作用域

没有块级作用域
    function test(){
        if(true){
           var a=2
       }
       console.log(a)  //2
    }
   test()

做过其他语言开发的程序员在初次接触这段代码就很奇怪,为什么可以访问到a变量if语句块里面定义的变量为什么外面可以访问?

这也算的上JS都有的特性吧,语言本身只存在函数作用域和全局作用域(个人认为全局作用域也可以称为全局匿名函数作用域),故语言本身就没有块级作用域,也就理解为什么if语句块里面定义的变量在外面可以访问喃,因为a变量属于 test函数所产生的函数作用域里面,所以可以在test函数内部任何位置访问。同理forwhile等其他语言生成块级作用域的地方,JS都不会生成块级作用域。

在ES6 出来之后就改变这样的规则,但是仅次于是letcount定义的变量才能产生块级作用域,var定义变量作用域的规则还是照旧。

原型和继承

给构造函数加上返回值

在JS基础里面我们知道,构造函数会有一个默认返回值,就是当前环境的this,但是当我们自定义返回值会怎么样喃?

 function Test(){
     this.name="李白"
     return "返回值"
 }
 let test=new Test()
 console.log(test)  // Test {name: "李白"}

预期效果,我们自定义的返回值并没有干扰到构造函数所返回的值,还是返回的是或新建的对象,但是看一下下面这种情况喃

 function Test(){
     this.name="李白"
     return {
         name:"杜甫"
     }
 }
 let test=new Test()
 console.log(test)  // {name: "杜甫"}   

我们自定义的返回值替代了构造函数的返回值,和上面截然不同

总结:当对构造函数自定义返回值时,如果自定义的返回值是基本类型数据,并不会干扰到构造函数的原本返回值。但是如果自定义的返回值是引用类型,那么就会替代构造函数的原本返回值

修改对象继承的原型对象

 let pro={
     a:1
 }
 let test1=Object.create(pro)
 let test2=Object.create(pro)
 test1.a=999
 console.log(test1.a)   //999
 console.log(test2.a)   //1

tese1test2继承自一个对象,当test1去修改a并没有影响到test2的a。这道理很简单,因为test1并没有修改到pro的a值

 let pro={
     a:[1,2,3]
 }
 let test1=Object.create(pro)
 let test2=Object.create(pro)
 test1.a.push(5)
 console.log(test1.a)   // [1, 2, 3, 5]
 console.log(test2.a)   // [1, 2, 3, 5]

很显然如果a 是引用类型又可以修改了,这也是为什么建议不要在原型对象上放属性的原因

持续更新中...