JavaScript基础
# JavaScript基础
用看得懂的话写de教程。
# 数据类型(8种) Type
JavaScript的8种原始数据类型包括Number、String、Boolean、Null、Undefined、Symbol(ES6新增)、BigInt(新增),此外有Object类型。函数被认为是可执行的object类型变量,但typeof会返回'function'。
# Number
不区分整数和浮点(内部区分),注意浮点计算结果是不精确的(由于浮点数的计算方式,如0.1+0.2=0.30000000000000004
),对浮点数不建议直接用等号判断两值相等,应当看他们的差是否小于某个值(如0.001)。Infinity(超过Number最大值)、NaN(Not a Number)也是合法的Number。
关于NaN
NaN是特殊的Number,它也不等于自己,只能通过isNaN()判断。
# String
单引号‘’或双引号""包裹的字符串。
# Boolean
Boolean包括'true'、'false'两种取值。比较运算符、&&、||与!都可能产生布尔值。
转换结果为false的取值
NaN、null、undefined、0与空字符串转Boolean的结果都为false。
# Null
null表示空值,undefined表示未赋值,undefined可以用于判断函数参数是否传递。他们是两种数据类型。
# Undefined
访问对象未声明的属性、函数未接受到的参数、未初始化的变量都会返回undefined。
null和undefined的注意事项
null是保留字,而undefined不是。
如果访问不存在的变量,会报错"var is not defined";访问已声明但未赋值的变量才会得到undefined。
typeof null的结果是object,但null也是一种基本类型而非object,这个混淆是由于typeof以内存低位判断数据类型,object和null的低3位都是0。
# Symbol ES6
用于产生唯一标识,除了自己等于自己,两个完全相同的symbol不相等,常用于对象属性、声明唯一常量、定义私有属性。也可以用Symbol.for()创建symbol,如果参数一致,创建的symbol相等。
let s = Symbol("")
let s1 = Symbol("")
s == s1//false
s === s//true
let s2 = Symbol.for("")
let s3 = Symbol.for("")
s2 == s3//true
你可以通过以下方式获取Symbol的description(无需记忆)。
let s2 = Symbol("love")
let obj = {[s2]:"and peace"}
obj//{ [Symbol(love)]: 'and peace' }
Object.getOwnPropertySymbols(obj)//[ Symbol(love) ]
Reflect.ownKeys(obj)//[ Symbol(love) ]
Symbol定义了对象的许多实用方法,包括[Symbol.Iterator]、[Symbol.match]、[Symbol.replace]、[Symbol.split]、[Symbol.toPrimitive]、[Symbol.toStringTag]等。toStringTag方法甚至能改变对象的toString方法。
# *BigInt ES6
在数字末尾加n可声明BigInt,其可操作大于Number所能表示最大数的数(2^53)。该类型尚在提案过程中,在新版Chrome与Node中得到实现。
带小数的运算会被取整。
let Num = BigInt(Math.pow(2,63))
let Num2 = 100n
# Object
键-值对的无序集合。键(key)只能是字符串类型,值(value)可以是任意类型。'.'可以用于表示键路径,比如obj.key或obj.obj.a。
Object是引用类型,存储的是指针,而其他基本类型存储值。
let obj = {key:'value',
obj:{
a:'a'
}}
# Array
用[]或new Array()创建,数组可以包含任意类型元素并且提供了相当多的方法。
# Date
Date类型提供了丰富的与时间、日期相关的方法,Date()返回当前日期的字符串。
# RegExp
正则表达式对象。
# Map&Set ES6
ES6新增的数据结构。
Map是一组key-value对结构,key不能重复,否则只保留最新的值。与对象只支持string与symbol相比,Map 的key支持任意类型。
let students = new Map([['Lucy',90],['Peter',80],['Bill',85]])
students.get('Lucy')//90
students.has('Peter')//true
students.delete('Peter')//true
students.get('Peter')//undefined
students.set('Bill',90)//Map { 'Lucy' => 90, 'Bill' => 90 }
students.get('Bill')//90
Set类似集合,由一组不重复的key组成,否则只保留一个。
let foods = new Set(['Chicken','Noodles','Rice'])
foods.add('fish')//Set { 'Chicken', 'Noodles', 'Rice', 'fish' }
foods.delete('Noodles')//true
foods.has('Rice')//true
# 获取基本类型:typeof
typeof var
可以获取变量类型,不与ES规范一一对应,其返回值是以下结果之一:
返回数据类型 | 对应基础类型 |
---|---|
number | Number |
string | String |
boolean | Boolean |
object | Object Null |
undefined | Undefined(该变量不存在) |
symbol | Symbol |
function | Object |
bigint | BigInt |
# 获取实例类型:instanceof
instanceof用于判断检测对象的类型,包括"Array"、"Function"、"Object"及自定义类/构造函数等。
此外Object.prototype.toString.call()可以准确打印出Null的类型。也可以通过访问".constructor"获取构造函数判断类型。
let a = []
typeof a//'object'
a instanceof Array//true
class y{}
let t = new y()
t instanceof y//true
# 动态类型&类型转换
作为动态语言,JS允许同一个变量在不同时间用作不同类型。
# 使用JavaScript函数转换
例如全局方法(构造函数)String()、Number()、Date()以及变量的toString()方法等。不同类型还会有独有的方法比如Date变量的getDate()、getDay(),Number变量的toPrecision()等。
# 使用JavaScript自动转换
变量类型会根据需要发生类型转换,例如:
5 + null //5 因为null=0
5 + undefined //NaN 因为undefined转为数字是NaN
"0"+ null //"0null" 因为null="null"
"5" + 1 //"51" 因为1="1"
1 + "5" //"51" 因为1="1"
"5" - 1 //4 因为"5"=5
[1,2] + 1//"1,21" 数组先转字符串,再加"1"
if("str"){
//这里的代码将会执行,因为“str”可以转为true
}
可以观察到含字符串类型会转为字符串,没有或不能转字符串的话转数字(除加号以外,结果转数字)。
自动转换有一些基础规则,比如Boolean值的转换:true等于1,false等于0,空字符串、空数组和null等于0,非纯数字字符串转为NaN等。
有趣的是,"0"可以转为Boolean的“true”,但“0”转为数字0之后再转Boolean就会变成“false”。空数组也可以转“true”,转数字之后也为0。
# 基本类型&引用类型
JS通过引用操作对象,引用类型变量的复制不会引起对象复制(可以理解为复制指针),但基本类型变量的复制会在内存中产生两份变量。
let a={}; let b=a; a.x=0; //a===b
let c=5; let d=c; c=6; //c!==d
# 基本类型包装
除了Object类型存储的是引用,所有类型都是基本类型(存储值),但除了null和undefined,他们都像对象一样拥有自己的方法。这不是因为基本类型具有方法,而是在调用基本类型的方法时,JS引擎自动包装了基本类型,调用结束后销毁对象。
因此,向基本类型添加属性是无效的,因为添加完成后临时对象即被销毁,但可以向其原型添加属性和方法。
var str = 'str'
str.toUpperCase()
/*
相当于做了这些事
var _str = new String(str)
str = _str.toUpperCase()
*/
# 变量声明 Declaration
# var
变量用var声明,不用var则作为全局变量。var声明的变量处于全局作用域或函数作用域。
# 变量提升 Hoisting
用var声明的变量,可以在声明语句之前使用,但不会初始化(赋值)。因此访问他们虽然不会报错,但会得到undefined。
# let&const ES6
ES6中新增了let与const关键字,分别代表块级作用域中的变量与常量,同时不允许重复声明,没有变量提升。
const定义的对象并非常量,const仅保持变量的值(即指针)不变,如果要声明对象常量,则应该使用Object.freeze()。
# 块级作用域
由{}包裹的代码块。在for循环中,()与{}是父子块级作用域,也就是说{}用let或const声明的变量不会影响for循环计数。
块级作用域没有变量提升,可以防止在函数内使用上级变量时,后面声明的变量意外覆盖上级变量。
使用var声明变量:
var h=10;
function print(){
console.log(h)
var h;//覆盖了上级变量
}
print()//undefined
使用let声明变量:
var h=10;
function print(){
console.log(h)//暂时性死区
let h;//与当前作用域绑定,声明之前不可读取
}
print()//ReferenceError: Cannot access 'h' before initialization
同时块间的隔离有助于减少冲突和出错。此前,JS只能用函数作用域来隔离变量,常用的方式就是匿名立即执行函数(匿名IIFE)。
(function(){var hours=12}())
//等同于
{let hours=12}
暂时性死区 temporal dead zone
let和const声明的变量会与代码块绑定,在声明前不能使用同名的上级环境变量,否则会引发报错。
# function
function关键字用于声明函数。
# class ES6
class关键字用于声明类。
# import ES6
import关键字用于导入其他模块的变量,这类变量被引用它的所有文件共享。
# 全局变量
var和function声明的全局变量会成为顶级对象(如window、global、self)的属性,而let、const、class、import不会。
# 判断变量是否存在
由于直接使用未声明的变量会报错,可以用try{}包裹代码,也可以用typeof variable == "undefined"
判断。
# 命名规范
变量名由26字母的大小写、数字、“$“和”_“组成,不能用数字开头。甚至支持中文,但不建议使用,避免引发麻烦。
# 函数 Function
函数都是Function对象的实例,相比普通对象,函数多了可被调用的特征。
'Function'与‘function’的不同
‘Function’是JS的内置对象,而'function'是一个声明函数的关键字。
和其他语言函数的区别
由于JS是弱类型语言,函数无法指定形参类型与返回类型,同时也无法限制传入参数的个数,因此没有重载的特性。函数内部可以通过arguments对象获取实参。
# 函数声明
推荐使用函数表达式let func=()=>{}
或let func=function(){}
为变量赋值,因此函数声明也遵循变量声明的规则。如果用function func(){}
直接声明函数,ES5中函数声明能完整地提升,ES6虽然规定了行为类似let,但实际可能会先赋值为undefined,不同环境可能有不同的处理。
# 参数传递
JS中任何参数都只能通过值传递,不存在引用传参。传递对象时,实际上复制了“指针”。因而对指针本身做修改不会反映到对象上,指针只用于访问对象,但本身不是对象。
那些支持引用传参的语言,修改引用同时也会修改对象,这是JS和他们的不同。
# 对象 Obejct
# 属性
普通对象属性(数据属性)拥有4个描述符(Descriptor),分别是Configurable、Enumerable、Writable、Value,前三项默认值为true,Value默认值为undefined。
Configurable控制属性特性(包括getter与setter,但value除外)能否被修改、属性能否被delete,且只能从true变为false。Enumerable控制属性能否被for-in遍历。Writable和Value字面意思。
访问器属性拥有2-4个特性,分别是Configurable、Enumerable、Getter?、Setter?。getter和setter的作用与其他语言一致。
属性描述符通过Obejct.defineProperty()、Object.defineProperties()、Object.getOwnPropertyDescriptor()写入和读取。
# 运算符 Operator
# 比较运算符
== === > < >= <= != !==
==表示在类型转换后相等,===表示类型和值都一样。除非需要用到==的特性,否则建议用===比较。
比较对象
对象的比较与原始值不同,比较的是引用,因此两个完全相同的数组不相等,除非他们是对同一处的引用。
# 赋值运算符
+= /= *= -= %= 等
和其他语言用法相同。
# '与'运算符
&&
‘与’运算符,如果左边表达式的值是false或可以转为false则返回左边表达式的值,否则返回右边表达式的值。
Boolean角度:&&只有当两边都为true,结果才为true,如果左边结果为false,右边不会判断。
# '或'运算符
||
‘或’运算符,如果左边表达式的值是true或可以转为true则返回左边表达式的值,否则返回右边表达式的值。
Boolean角度:||只有当两边都为false,结果才为false,如果左边结果为true,右边不会判断。
# 扩展运算符ES6
...
ES6中的扩展运算符,用在数组或对象前表示取出所有项或属性。
# 用于对象
let obj={a:1,b:2}
let newObj={...obj,c:3}//{ a: 1, b: 2, c: 3 }
# 用于数组
# 赋值
生成数组的拷贝。
let arr=[0,1,2]
let newArr=[...arr,3,4]//[ 0, 1, 2, 3, 4 ]
let arr_copy=[...arr]
# 解构赋值
结合解构赋值,它还提供了生成数组的方法。
let [...arr_copy]=arr
let [ar1,...ar2]=[0,1,2,3]
ar1//0
ar2//[ 1, 2, 3 ]
let [...ar3,ar4]=[0,1,2,3]
//SyntaxError: Rest element must be last element
扩展运算符只能用在最后一项。
# 一个分号引发的惨案
let val = func()//;
[a,b]=[b,a] 或 [a[c],b[c]]=[b[c],a[c]]
如果缺少分号,JS解析会出现错误,原因是两行连在一起也符合语法规则。这种情况下
# 用于函数参数
除了为数组赋值,还支持作为函数参数。
let arr=[0,1,2]
function add(a,b){
return a+b
}
add(...arr)//1
任何含Iterator接口的对象都可以通过扩展运算符转为真正的数组。详情见iterable接口。
# 基本运算
! + - * / % ++ --
可以用于各种类型间的运算,不限于数字。
# 位运算符
<< >> ~ & |^ >>>
和其他语言用法相同。JS内部虽然存储64位数值,但对程序员透明,位运算符的结果和32位数值运算的结果一致。
# 三目运算符
expression1 ? expression2 : expression3
等同于
((expression1, expression2, expression3) => {
if (expression1) {
return expression2;
} else {
return expression3;
}
})();
'expression'与'statement'
expression即表达式,是一段有值的语句,比如变量x,函数f或者其执行结果f()。其他语言中函数一般不能视作expression,但JS中函数是对象,这是特例。
statement意为声明,比如let a=10
,或者if(code){code}
,这些语句不返回结果,而是提醒处理器该做什么。
# 方括号
[]
属性访问器。最常用的是表示数组[1,2,3]
与数组下标arr[1]
,也可以作为对象的属性名obj['key']
,支持使用变量作为属性名obj[key]
(key不仅可以是符合规则的字符串,也可以是Symbol)。
# 点运算符
.
属性访问器。点运算符的功能是[]的子集,当属性名为常量时可以用于设置、获取对象属性obj.key
。
# 循环体 Loop
# while
while(expression){}
# do...while
do{statement}while(expression)
# forEach
Array.prototype.forEach(function(currentValue, index, arr), thisValue)
由于是以传入函数的形式遍历,forEach无法使用return从外部函数体返回,由于是数组的一种方法,也不支持break跳出循环。
# for…in
for(let item in obj)
for…in支持遍历各种对象,它的item是对象的key,总是string类型。这种方法不稳定,不同时候结果的顺序可能不一致。如果用于数组,还有一个问题是所有属性也包括数组元素以外的自定义属性。
# for…of ES6
for(let item of obj)
首先,它没有for…in的缺点,其次,也没有forEach的缺点。for…of支持数组、字符串、Set、Map和其他有iterable接口的对象,但不支持普通对象(会提示is not iterable
)。
# 循环控制
在循环体中使用continue或break可以跳过一次循环/退出循环。对于嵌套循环结构,JS提供了label关键字标记循环。
label: statement
loop1: while (true) {
loop2: while (true) {
break loop1;
}
}
# 控制语句 statement
# If语句
if(expressionA){}else if(expressionB){}...else{}
# Switch语句
switch(expression)case expression:statement break;...default:
# 部分关键字 Keywords
# new
从构造函数派生出对象,构造函数的this指向创建的对象。
# delete
用于删除对象属性,不可用于删除对象。
# throw
抛出异常。通常结合try...catch使用。
# async&yield
被异步函数的一种写法需要。作为语法糖可以被链式调用替代。
# 错误处理 Error Handling
# try...catch语法
# try...catch
try {
//insert code here
a.b=c
}
catch(err) {
console.error("Error catched: "+err)
}
# throw
抛出自定义错误。
try {
throw 'Number is too big'
}
catch(err) {
console.error(err)
}
# finally
无论是否发生错误都会执行的语句。
try {
}
catch(err) {
}
finally {
}
# Error对象与类型
如果是内置错误(非throw抛出),抛出的是一个Error对象。包括name和message属性。
错误类型(Error.name) | 触发条件 |
---|---|
ReferenceError | 引用了尚未声明的变量。 |
SyntaxError | 语法错误。 |
TypeError | 类型错误。如xxx is not a function 。 |
URIError | URI函数的独有报错。 |
rangeError | Number方法的独有报错。 |
EvalError | eval() |
# 参考目录
- this https://www.cnblogs.com/xiaohuochai/p/5735901.html
- JavaScript https://www.liaoxuefeng.com/wiki/1022910821149312
- ECMA Script 6 http://es6.ruanyifeng.com/
- Execution Context https://juejin.im/entry/58edde2761ff4b00581b93ff
- Symbol https://www.cnblogs.com/diligenceday/p/5462733.html
- MDN
- runoob