这是我参与8月更文挑战的第7天,活动详情查看:8月更文挑战
前言
在日常开发和学习中,我总能遇到一些奇怪的问题,所以记录了下来!
基本语法和变量
连续定义变量
在日常开发中,我们经常会使用到定义多个变量的场景
var a=b=3
但是这到了JS解释器眼里就会变成
b=3
var a=b
因为 =运算符是自右向左运算的,所以会先执行b=3,再执行var a=b
眨眼看去也没什么错呀,也只是在定义b时未使用var,熟悉全局作用域的人应该知道在JS中未用var、 let、count定义变量,那么变量默认是定义到全局的。
来看看例子:
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
发现了什么,明明修改了str的length属性,再次访问却还是原来的值!!!
我们可以将语句拆开来看,其实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函数内部任何位置访问。同理for、while等其他语言生成块级作用域的地方,JS都不会生成块级作用域。
在ES6 出来之后就改变这样的规则,但是仅次于是let、 count定义的变量才能产生块级作用域,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
tese1和test2继承自一个对象,当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 是引用类型又可以修改了,这也是为什么建议不要在原型对象上放属性的原因
持续更新中...