通俗重制系列--JavaScript知识点
基本语法
条件语句
JavaScript 提供if
结构和switch
结构,完成条件判断,即只有满足预设的条件,才会执行相应的语句。
if结构
1.规则
- 先判断一个表达式的布尔值,然后根据布尔值的真伪,执行不同的语句。
- 所谓布尔值,指的是 JavaScript 的两个特殊值,true表示真,false表示伪。
- 如果表达式的求值结果为true,就执行紧跟在后面的语句;如果结果为false,则跳过紧跟在后面的语句。
2.基本形式
if (布尔值){ 语句 } // 或者 if (布尔值) 语句;
3.关于 =
- 赋值表达式(=):x=y 把y的值赋给x!一直是true!
- 严格相等运算符(===):x===y x等于y 优先采用
- 相等运算符(==): x===y x等于y
if...else 结构:
if代码块后面,还可以跟一个else代码块,表示不满足条件时,所要执行的代码。
1.规则:
- 先判断一个表达式的布尔值,然后根据布尔值的真伪,执行不同的语句。
- 如果表达式的求值结果为true,就执行紧跟在表达式后面的语句1;如果结果为false,则执行else里的语句2
2.基本形式
if (表达式){ 语句1 } else{ 语句2 }
3.语句1里面可以有嵌套的if...else 结构
var m = 1; var n = 2; if (m !== 1) if (n === 2) console.log('hello'); else console.log('world');
上面代码不会有任何输出,else代码块不会得到执行,因为它跟着的是最近的那个if语句,相当于下面这样。
if (m !== 1) { if (n === 2) { console.log('hello'); } else { console.log('world'); } }
4.语句2里面可以有嵌套的if...else 结构
// 对一个变量多次进行判断 if (表达式一) { 语句一; } else { if (表达式二) { 语句二; } else { if (表达式三) { 语句三; } else { 语句四; } } } // 去掉一些大括号,等同于 // 最推荐写法 if (表达式一) { 语句一; } else if (表达式二) { 语句二; } else if (表达式三) { 语句三; } else { 语句四; } function fn(){ if (表达式) { return表达式 } if (表达式) { return表达式 } return表达式 }
5.else代码块总是与离自己最近的前面的那个if语句配对。
6.缩进也可以很变态,如面试题常常下套
a=1 if(a===2) console.log('a') console.log( 'a等于2') // a等于2 等同于 a=1 if(a===2){ console.log('a') } console.log( 'a等于2')
程序员戒律:使用没有歧义的写法
最推荐的写法
if(表达式){ 语句 }else if(表达式){ 语句 } else { 语句 }
次推荐写法
function fn(){ if (表达式){ return 表示式 } if (表达式){ return 表达式 } return 表达式 }
return包含后面都不执行的意思,所以此时可以省略else,因为else的意思就是如果前面都没执行,就执行else之后的。
switch语句
1.基本结构
switch (fruit) { case "banana": // ... break; case "apple": // ... break; default: // ... }
上面代码根据变量fruit的值,选择执行相应的case。如果所有case都不符合,则执行最后的default部分。
注:
case
代码块之中没有break
语句,导致不会跳出switch
结构,而会一直执行下去switch
语句部分和case
语句部分,都可以使用表达式。
问好冒号表达式(三元运算符 ?:)
1.基本形式
(表达式) ? 语句1 : 语句2
2.规则:如果“表达式”为true,则执行“语句1”,否则执行“语句2”
举例:
function max(a,b){ if(a>b)return a; else return b; } //等同于 function max(a,b){ return a>b ? a:b }
便捷性: 能用问号冒号表达式的时候就不用if...else
&&短路逻辑
也是用来替代if else
A && B && C && D 取第一个假值或D,并不会取true或false
举例:
if(window.f1){ console.log('f1存在') } //等同于 window.f1 && console.log('f1存在')
fn && fn()
fn存在就调用fn,不存在就不调用
|| 短路逻辑
A || B || C || D 取第一个真值或D,并不会取true或false
举例:
a||b //等同于 if(!a){ b }else{a} //如果a不存在,就执行b,否则还是a
a = a||100 //如果a存在就是a,如果a不存在就令a为100 //如果变量a存在,变量a就为第一个真值,再把变量a赋值给变量a,a还是a自己 //a不存在,那么第一个a不是真值,就执行最后一句a=100。给a一个保底值。 等同于 if(a){ a=a } else{ a=100 // 保底值 } //如果a不存在就令a为100,如果a存在就令a还是为a 等同于 if(!a){ a=100 // 保底值 } else{ a=a }
循环语句
while(不推荐)
1.基本结构
while (表达式) {循环体语句}
2.规则:
- 判断表达式的真假
- 当表达式为真,执行语句,执行完再判断表达式的真假
- 当表达式为假,执行后面的语句
注意:
容易死循环 (下面的代码将循环100次,直到i等于100为止)
var i = 0; //初始化表达式 while (i < 100) { //判断表达式 console.log('i 当前为:' + i); //循环体语句 i = i + 1; //递增表达式 }
浮点数不精确 (会进入死循环。因为浮点数a永远不会等于1)
while(a !== 1){ console.log(a) a=a+0.1 }
for循环
1.基本结构
for (初始化表达式; 判断表达式; 递增表达式) { 循环体语句 }
2.规则
- 先执行初始化表达式
- 再判断表达式的真假
- 如果为真,就执行循环体语句,然后执行递增表达式
- 如果为假,就直接退出循环,执行后边的语句
举例:
var x = 3; for (var i = 0; i < x; i++) { console.log(i); } // 0 // 1 // 2 // 最后循环执行完i=3
上面面代码中,初始化表达式是var i = 0,即初始化一个变量i;判断表达式是i < x,即只要i小于x,就会执行循环;递增表达式是i++,即每次循环结束后,i增大1。
for(var i=0;i<3;i++){ setTimeout(()=>{ console.log(i) },0) } //3个3 /* i=0满足i<3,所以执行循环体打出i,但要过一会。 以此类推 i=1满足i<3,所以执行循环体打出i,但要过一会。 i=2满足i<3,所以执行循环体打出i,但要过一会。 i=3不满足i<3,结束循环。 for循环不结束,就不算过一会! 现在循环结束了,过一会了,终于要打出i了,而且还要打出三次,因为此时i=3了。所以打出了3个3 */
所有for
循环,都可以改写成while
循环。上面的例子改为while
循环
var x = 3; for (var i = 0; i < x; i++) { console.log(i); } // 0 // 1 // 2
上面代码中,初始化表达式是var i = 0
,即初始化一个变量i
;测试表达式是i < x
,即只要i
小于x
,就会执行循环;递增表达式是i++
,即每次循环结束后,i
增大1
var x = 3; var i = 0; while (i < x) { console.log(i); i++; }
break和continue
break语句和continue语句都具有跳转作用,可以让代码不按既有的顺序执行。
break语句用于跳出代码块或循环。
var i = 0; while(i < 100) { console.log('i 当前为:' + i); i++; if (i === 10) break; } //上面代码只会执行10次循环,一旦i等于10,就会跳出循环。
for循环也可以使用break语句跳出循环。
for (var i = 0; i < 5; i++) { console.log(i); if (i === 3) break; } // 0 // 1 // 2 // 3 //上面代码执行到i等于3,就会跳出循环。
continue语句用于立即终止本轮循环,返回循环结构的头部,开始下一轮循环。
var i = 0; while (i < 100){ i++; if (i % 2 === 0) continue; console.log('i 当前为:' + i); } //上面代码只有在i为奇数时,才会输出i的值。如果i为偶数,则直接进入下一轮循环。
如果存在多重循环,不带参数的break语句和continue语句都只针对最内层循环。
label
JavaScript 语言允许,语句的前面有标签(label),相当于定位符,用于跳转到程序的任意位置,标签的格式如下。
label: 语句
举例:
foo:{ console.log(1); break foo; console.log('本行不会输出'); } //foo是一个标签,表示一个代码块 //1
{ foo:1; } //这是一个代码块,代码块里有一个标签foo,标签foo的内容是1
数据类型
数值(number)
数值的存储方式
JS中,数字是用64位浮点数的形式存储的。(二进制)
其中符号占1位,指数部分有11位,范围从-1023到+1024,有效数字有 52 位,需要省略开头的 1 。 例如:0.5的存储方式是 0|-1|0 。
浮点数的范围
(1)最大值
- 把指数部分11位和有效数字的52位全设为1(拉满),可得最大的二进制数字
- 换成十进制为 Number.MAX_VALUE: 1.7976931348623157e+308
(2)最小值
- 指数负方向拉满、有效数字最小1,得到最小的二进制数字
- 换成十进制为Number.MIN_VALUE: 5e-324
精度
- 最多只能到52+1个二进制位表示有效数字
- 2^53对应的十进制是9后面15个零
- 所以对于十进制来说15位数字都能精确表示
- 16位有效数字如果小于90开头,也能精确表示
- 91 10000000000001就存不下来
数值的表示法:
- 整数 1
- 小数 0.1
- 科学计数法 1.23e4 (e4就是乘以10的4次方)
- 八进制(用得少)0123,00123,0o123
- 十六进制 0x3F 0X3F (用得少)
- 二进制 0b11 或0B11 (用得少)
数值的进制
JavaScript 对整数提供四种进制的表示方法:十进制、十六进制、八进制、二进制
- 十进制:没有前导0的数值
- 八进制:有前缀0o或0O的数值,或者有前导0、且只用到0-7的八个阿拉伯数字的数值。如0123或00123或0o123
- 十六进制:有前缀0x或0X的数值。如0x3F或0X3F
- 二进制:有前缀0b或0B的数值。如0b11或0B11
特殊数值
- 正0 和 负0都等于 0, 但是是三个数
- 无穷大: Infinity 、+Infinity 、-Infinity
- 无法表示的数字:NaN(Not a Number),但他是一个数字 。(比如0除以0,返回NaN) NaN不等于任何值,包括NaN。
方法
parseInt
方法用于将字符串转为整数。parseFloat
方法用于将一个字符串转为浮点数。isNaN
方法可以用来判断一个值是否为NaN
isFinite
方法返回一个布尔值,表示某个值是否为正常的数值
布尔(bool)
只有两个值,true和false 下列运算符会返回布尔值:
- 前置逻辑运算符:
! (Not)
- 相等运算符:
===
,!==
,==
,!=
- 比较运算符:
>
,>=
,<
,<=
如果 JavaScript 预期某个位置应该是布尔值,会将该位置上现有的值自动转为布尔值。转换规则是除了下面六个值被转为false,其他值都视为true
undefined
null
false
0
NaN
- ''(空字符串)
这五个 falsy 值代表假的。(还有false)
undefined和null
undefined 和 null 是两种空类型,区别如下:
- 声明变量但是没有赋值,默认值是undefined,不是null。
- 函数没有return,那么默认return undefined,而不是 null。
- 习惯上,非对象的空值写为undefined,把对象的空值写为 null。
字符串(string)
1、写法
- 单引号 'hi'
- 双引号 "hi"
- 反引号 ``
引号不属于字符串的一部分
2、转义
例如 'it's ok' ,JS引擎会认为 'it' 就结束了。
正确写法
- 转义: 前面加斜杠 \ ,例如 'it's ok'
- 外面用双引号:"it's ok"
- 外面用反引号
转义——用另一种写法表示你想要的东西
- ' —— ' 单引号
- " —— " 双引号
- \n —— 换行
- \r —— 回车
- \t —— tab 制表符
- \ —— \斜杠
- \uFFFF —— 对应的Unicode 字符
- \xFF —— 前 256 个Unicode 字符
- 多行字符串:外面用反引号
3、字符串长度
string.length —— 即可看见字符串string的长度 例如:'\n\r\t'.length //值为3
通过下标可以读取字符(类似数组) 例如:let s='hello';s[0] //值为"h"
4、base64转码
- window.btoa(字符串)—— 正常字符串转为Base64编码的字符串
- window.atob(字符串) —— Base64编码的字符串转为原来的字符串
- 五、symbol符号
三种声明方式
var a = 1
是过时的、不好用的方式let a = 1
是新的,更合理的方式const a = 1
是声明时必须赋值,且不能再改的方式a = 1
最后这种方式是错误的,不准这样声明
let声明
- 遵循块作用域,适用范围不能超过{}
- 不能重复声明
- 可以赋值,也可以不赋值
- 必须先声明再使用
- 全局声明的let变量,不会变成window属性
- for循环配合let 有奇效
const声明
- 跟let规则一样
- 只有一条不同,声明时就要赋值,赋值后不能改
注意:声明变量的同时,也指定了类型,但是值和类型都可以随时变化
类型转换
1、number => string
String(n)
n+''
''+n
2、string => number
Number(s)
parseInt(s)
parseFloat(s)
s-0
+s
3、x => bool
Boolean(x)
!!x
一个感叹号可把任何东西取反布尔值,再来一个就是反布尔值的反布尔值,也就是原始布尔值
4、X => string
String(x)
x.toString()
- 数字1不可用上面的,因为默认1.后面应是小数。bug!
(1).toString()
1..toString()
1 .toString()
对象
定义:
无序的数据集合
建制对的集合
写法:
- 简便写法:
let obj = { 'name': 'frank', 'age': 18 }
- 正规写法:
let obj = new Object({'name': 'frank'})
- 匿名写法:
console.log({ 'name': 'frank, 'age': 18 })
注意:
- 键名是字符串!
- 键名是字符串,不是标识符,可以包含任意字符
- 引号可省略,省略之后就只能写标识符
- 渲算引号省略了,键名也还是字符串(重要)
对象的属性
- 属性名:每个key都是对象的属性名(property)
- 属性值:每个 value 都是对象的属性值
奇怪的属性名:
所有属性名都会自动变成字符串
let obj = { 1: 'a', 3.2: 'b', 1e2: true, 1e-2: true, .234: true, 0xFF: true }; /** 以上每个属性的key值: 1其实是'1' 3.2其实是是'3.2' 1e2其实是'100' 1e-2其实是'0.01' .234其实是'0.234' 0xFF其实是'255'
细节Object.keys(obj)
可以得到obj的所有key
变量做属性名:
如何使用变量做属性名?之前都是常量做属性名
let p1 = 'name' let obj = { p1 : 'frank'} //这样写,属性名为 'p1' let obj = { [p1] : 'frank' } //这样写,属性名为 'name'
- 不加 [ ] 的属性名会自动变成字符串。
- 加了 [ ] 则会当做变量求值。
- 值如果不是字符串,则会自动变成字符串
3、对象的隐藏属性:
- JS中每个对象都有一个隐藏属性__proto__
- 这个隐藏属性储存着其共有属性组成的对象的地址
- 这个由共有属性组成的对象window.Object.prototype叫做原型,也称为对象的根
- 也就是说,每个object对象的隐藏属性__proto__储存着原型window.Object.prototype的地址
- 每个对象都有原型
- 原型也是个对象,所以也有原型,这不过原型的原型为null(空,但是是存在的)
var obj= {} obj.toString() //居然不报错 //因为obj的隐藏属性对应的对象上有toString()
除了字符串,symbol也能做属性名
let a = Symbol() let obj = {[a]: 'Hello'}
对象属性的增删改查
删除属性
delete obj.xxx
或delete obj['xxx']
:即可删除obj的XXX属性,此时属性值当然也没有 注意比较区别:obj.xxx = undifined xxx
属性名还在,只是属性值变为undifined
举例:
(1)
注:只能用'xxx' in obj
查看属性名是否还在对象中:true表示在,false表示不在
(2)
①语句'xxx' in obj && obj.xxx === undefined
返回true,表示属性xxx还在obj中,而且属性xxx的值是undefined
②注意obj.xxx === undefined
不能断定'xxx' 是否为obj的属性。
读属性
查看一个对象的所有属性:
- 查看一个对象的所有自身属性:
Object.keys(obj)
- 查看一个对象的所有自身属性值:
Object.values(obj)
- 查看一个对象的所有自身的属性和值:直接对象名就行
ob
j或者Object.entries(obj)
- 查看自身+共有属性:
console.dir(obj)
- 查看共有属性: 自己依次用
Object.keys
打印出obj.__ proto_
- 判断一个属性是自身的还是共有的:
obj.hasOwnProperty('属性名')
( 返回true说明该属性是自身属性,返回false说明该属性是共有属性) 'key' in obj
查看属性名是否还在对象中:true表示在,false表示不在
查看一个对象的一个属性
- 中括号语法:
obj['key']
或obj['k'+'ey']
- 点语法:
obj.key
- 坑新人语法:
obj[key]
// 变量 key 值一般不为 'key'
举例
let list = ['name','age','gender'] let person = { name: 'yy', age : 18, gender : 'woman' } for (let i = 0; i < list.length; i++) { let name = list [i] console.log(person.name) //重点 } // 结果为person的第一个属性值yy*3
let list = ['name','age','gender'] let person = { name: 'yy', age : 18, gender : 'woman' } for (let i = 0; i < list.length; i++) { let name = list [i] console.log(person[name]) //重点 } // 结果为person的所有属性值yy、18、woman
写属性
1、修改或增加属性
(1)直接赋值
let obj = {name: 'frank'} // name 是字符串 obj.name = 'frank' // name 是字符串 obj['name'] = 'frank' ***obj[name] = 'frank' // 错,因 name 值不确定 obj['na'+'me'] = 'frank' let key = 'name'; obj[key] = 'frank' let key = 'name'; ***obj.key = 'frank'~~ // 错,因为 obj.key 等价于 obj['key']
(2)批量赋值
Object.assign(obj, {age: 18, gender: 'man'})
2、修改或增加公有属性
无法通过自身修改或增加共有属性
- let obj = {}, obj2 = {} 共有 toString
- obj.toString = 'xxx' 只会在改 obj 自身属性
- obj2.toString 还是在原型上
我偏要修改或增加原型上的属性
obj.__proto__.toString = 'xxx'
// 不推荐用__proto__
Object.prototype.toString = 'xxx'
- 一般来说,不要修改原型,会引起很多问题
3、修改隐藏属性
不推荐使用__proto__
let obj = {name:'frank'} let obj2 = {name: 'jack'} let common = {kind: 'human'} obj.__proto__ = common obj2.__proto__ = common
推荐使用 Object.create
let obj = Object.create(common) obj.name = 'frank' let obj2 = Object.create(common) obj2.name = 'jack'
规范大概的意思是,要改就一开始就改,别后来再改
数组
待补充,贴一篇大佬的文JS数组奇巧淫技 - 掘金 (juejin.cn)