目录
一、短路逻辑
什么是短路逻辑?
类似于使用if的条件判断,但短路逻辑是使用逻辑运算符来实现的。
短路逻辑分为两种:&& 和 ||
1、&&:条件&&操作。
只有当条件满足才会执行操作,条件不满足则不执行。
var age=18
age>=18&&console.log("恭喜你已经成年了!") //恭喜你已经成年了!
2、||:操作1||操作2||操作3...
只要有一个满足为true,则不会执行后面的操作,即操作1满足true时则不会执行后续操作,当操作1不满足时,看操作2是否为true...
var age=18
console.log(18 || ++age) //18
二、运算符的补充
在之前的博客中写过算术运算符、逻辑运算符以及比较运算符,现在补充两个常见的位运算符。
在说算术运算符之前,我们首先得知道,数字在计算机中是以二进制的形式保存着的。在了解了二进制的特性之后,再看下面的内容就会很容易理解了。
1、<<:零填充左移,也就是左移几位就在末尾添加几个0,不会影响符号位。
语法:m<<n,数字m向左移n位,即m*2的n次方,同时右边的空位填0。
var num=8
console.log(num<<2) // 8*2^2=8*4=32
上面的例子中,8的二进制为:1000,左移2位,就是在原本二进制的基础上,末尾添加两个0,就变成了100000,通过二进制转十进制,很容易得出左移后的结果为32。
2、>>:有符号右移,在移动位数的时候,保留符号位,向右移动指定的位数,左边的空位根据符号位的值来进行填充,右边原本的二进制数会被挤出去。
语法:m>>n,数字m向右移动n位,即m/2的n次方。
var num=8
console.log(8>>2) // 8/2^2=8/4=2
二进制中使用0表示正数,使用1表示负数,在上面的例子中,数字为正数,所以符号位是0,8使用二进制表示为1000,加上符号位就是01000,右移2位,不移动符号位,所以是00010(右移了2位,原本右边的0被挤出去2个,左边的空位根据符号位的值来进行填充,所以是3个0),所以最后的结果是2。
三、三目运算
三目运算也就是类似于简化版的if...else分支结构。
语法:条件?操作1:默认操作
条件是否为true?是就执行操作1:不是则执行默认操作。
var age=18
age>=18?console.log("你成年了"):console.log("你没有成年") //你成年了
var price=20
price>30?console.log("好贵"):console.log("还行吧") //还行吧
可以在默认条件处书写另一个三目运算,也就是三目运算的嵌套使用。
var age=20
age>25?console.log("你找工作了吗"):age>23?console.log("你大学毕业了吗"):age>=20?console.log("你现在还是学生?"):console.log("你太小了") //你现在还是学生?
上面执行的顺序就是:判断age是否大于25,不是则执行第一个:后面的age>23?,不是则执行第二个:后面的age>20?,满足条件,执行console.log("你现在还是学生?")。
小练习:使用三目运算判断a、b、c三个变量中的最大值。
var a=4,b=5,c=7
var res = a > b ? (a > c ? a : c) : b > c ? b : c;
console.log(res);
执行逻辑:a是否大于b?满足则判断a是否大于c?满足则是a最大,不满足则是c最大,因为a已经大于b了,不满则a大于c,说明是c比a大,即c最大。
如果不满足a>b,则说明b>a,并且判断b是否大于c,满足则是b最大,不满足则是c最大。
上面的括号括起来的部分是a>b?的操作1,括号后面的第一个:之后的部分是默认操作,默认操作中又嵌套了一层三目运算。
注意:在实际开发中,如果条件过于复杂,建议不使用三目运算,原因是因为到时候不利于开发人员的后续阅读与理解,条件复杂的话还是使用if...else书写更加清晰。简单的条件判断还是推荐使用三目运算。
四、条件分支结构的补充
在上一篇博客中,条件分支结构有:if if...else... if...else if...else
这次要补充的是switch分支结构。
什么是switch分支结构?根据你的选择来输出内容或者执行操作,有点类似于if...else if...else。
语法:
switch(条件){
case 值1:操作1;break;
case 值2:操作2;break;
case 值3:操作3;break;
......
default:
默认操作;break;
}
case后面的值与case之间使用空格隔开,选择哪个值是根据switch后括号内的条件来进行选择。
为什么要加break?加上break的原因是因为在执行当前选择的case操作之后需要停止后续其他case的操作,如果不加break,会在当前的操作执行完之后,继续执行后面的case,直到遇见break或者整个switch执行完毕。
var user = parseInt(prompt("欢迎使用10086查询系统: 1、查询话费⒉、查询流量"));
switch (user) {
case 1:
alert("正在查需话费");
break;
case 2:
alert("正在查询流量");
default:
alert("没有功能");
}
注意:default后面可以省略break,因为已经是最后一个了,不需要再加上break。
除了以上写法之外,如果有多个case的操作是相同的话,还可以将这些case写在一起
var year = parseInt(prompt("请输入年份"));
var month = parseInt(prompt("请输入月份"));
switch (month) {
case true:
alert("别乱输");
break;
case 1:
case 3:
case 5:
case 7:
case 8:
case 10:
case 12:
alert("这个月有31天");
break;
case 4:
case 6:
case 9:
case 11:
alert("这个月有30天");
break;
case 2:
(year % 4 == 0 && year % 100 != 0) || year % 400 == 0
? alert("这个月有29天")
: alert("这个月有28天");
break;
default:
alert("只要月份!");
}
在面试的过程中,有可能会问:if和switch的优缺点?
switch的优点:执行效率相对较高。
switch的缺点:不能做范围判断,需要知道执行结果后才可以使用
if的优点:可以做范围判断
if的缺点:执行效率相对较低。
在实际开发中,优化代码时可以将if尽量的转换为switch。
五、基本数据类型与引用数据类型的赋值区别
要引出赋值区别,得先举个例子:
var str1="123"
var str2=str1
console.log(str1) // 123
console.log(str2) // 123
str2="456"
console.log(str1) // 123
console.log(str2) // 456
上面的例子可以看出,一个基本数据类型的变量通过变量名赋值给另一个另一个变量,在另一个变量改变值之后,不会影响原变量。
var arr1=[1,2,3,4,5]
var arr2=arr1
console.log(arr1) //[1,2,3,4,5]
console.log(arr2) //[1,2,3,4,5]
arr2[arr2.length]=6
console.log(arr1) //[1,2,3,4,5,6]
console.log(arr2) //[1,2,3,4,5,6]
上面的例子可以看出,引用类型的变量通过变量名赋值给另一个变量后,另一个变量如果改变了值,原本的变量的值也会随之改变。
结论:基本数据变量的赋值,一方改变,另一方不会随之改变;引用类型变量的赋值,一方改变,另一方也会随之改变。
原因:基本数据类型和引用数据类型在计算机内存中存储位置的不同造成的。
基本数据类型的存储:变量名存储在栈中,值也存储在栈中,通过变量名赋值给另一个变量,实际上就是将值拷贝一份,相当于生成了另一个新的值。
引用数据类型的存储:变量名存储在栈中,值存储在堆中,栈中的变量名保存的是值在堆中的地址,通过变量名的赋值,实际上只是将值的地址复制了一份,两个变量保存的地址相同,意味着指向的是同一个值,只要通过其中一个变量对值进行了改变值的操作,那么通过其他变量获取到的也是改变之后的值了。即直接通过变量名的赋值方式的复制,也叫做浅拷贝。
六、引用类型变量内存的释放
之前有说过,内存的释放有两个方法:
1、如果是变量的话,直接给这个变量赋值null;
2、如果是函数的话,那么在函数执行结束之后,会自动的释放内存;
但引用类型稍稍有点不同。如果有多个变量指向的是同一个引用类型的值,当只有一个变量释放内存之后,不会影响其他的变量,其他变量依旧是指向的是该引用类型的值。因为释放的只是地址而已,不会影响保存在堆中的值。
那么如何完全释放引用类型的变量呢?这里首先得提到一个概念:垃圾回收器。
在垃圾回收器中有一个计数器,保存着当前有几个变量指向该引用类型的值。只有当计数器为0时,表示着没有变量再指向该值了,垃圾回收器就会把没有被变量保存数据地址的数据进行回收,也就是释放内存。
总结:
1、看清有几个变量指向的是同一个引用类型的数据,只有都释放了之后,才代表引用类型的数据真正被释放;
2、最好的方法还是封装在函数中,当函数执行结束之后,自动的释放内存;
七、作用域的分类
在个各种语言中都有这作用域的概念,我们先来看看标准的解释是什么:一段程序代码中所用到的名字并不总是有效/可用的,而限定这个名字的可用性的代码范围就是这个名字的作用域。(--百度百科)
翻译过来就是:一段代码中的变量名有时候并不是随时随地都能被使用,有的只能在某个范围内被使用,一旦超出这个范围使用就有可能会报错。这个范围就叫做作用域。
作用域分为三种:全局作用域、局部作用域、块级作用域
全局作用域:只要没有被{}包裹的部分就是全局作用域,既然是全局,那么变量可以随时随地被使用;
局部作用域:在function后的{}内的部分叫做局部作用域,其实也可以说是函数作用域。在这个作用域内声明的变量就只能在这个范围内使用,一旦超出范围就会报错:xxx is not defined。
块级作用域:在ES6之前,只要{}没有与function结合在一起,就是块级作用域
注意:
1、在块级作用域中,使用var定义的变量是全局变量(产生了变量提升);
2、在局部作用域中,使用var定义的变量是局部变量;
3、不论是局部作用域还是块级作用域,只要没有使用let或者var来声明变量,那么就都是全局变量(但最好不这样,可能会造成全局污染)
八、函数的补充
在之前的博客中只是简单介绍了函数的定义和调用,还有个很重要的问题没有解决,那就是:如何在函数外部使用函数内部进行了某些操作之后得到的结果/变量?
解决办法:使用关键字return。
该关键字也只能使用在函数中,原本的意思是返回,表示着结束函数。当return跟着一个值或者变量的时候,表示结束函数并将该值/变量的值返回到外部作用域中,在调用函数的前面需要定义一个变量来接收这个返回值。
function f1(){
var age=18
return age; //将age的值返回
}
var res=f1() //调用函数并接收返回值
console.log(res) //18
注意:如果return后没有跟着其他的值的话,有一个默认的返回值:undefined
function f1(){
return
}
var res=f1()
console.log(res) //undefined
在函数中如果有条件分支结构,在条件分支结构中如果出现了return,那么就不会执行后面的代码,直接退出函数。
function f1(){
var age=18
if(age>=18){
console.log("你成年了")
return
}
console.log("代码执行结束")
}
f1() //你成年了
九、为什么有的基本数据类型能够使用.语法?
有JS基础或者其他语言基础的朋友应该知道,. 语法其实只针对对象类型的数据,其他类型的数据使用 . 语法就可能会报错。但是我们在使用的时候惊奇的发现,保存着基本数据类型的变量能够通过 . 语法使用某些方法,这是为什么?
以JS为例:
原因:在JS设计的时候,大佬们考虑到程序员有可能会拿字符串或者数字等数据进行某些操作。为了方便我们程序员,就在我们使用字符串等基本类型(原始数据类型)进行 . 操作时,悄悄地将原始数据类型转变为对象类型。这种转变叫做:包装类型。
什么是包装类型?
专门封装原始数据类型得值,将原始数据类型转变为引用类型的对象。(即封装了属性和方法)
何时使用包装类型?
当我们使用 . 操作时,就会试图去包装原始数据类型。
何时释放为原始数据类型?
当方法调用结束之后,包装类型就会自动释放。
注意:undefined和null不可以使用 . 操作。原因是因为底层没有为这两个数据类型提供包装类型,一旦使用就会报错。