Chapter 03 JavaScript Language
Syntax
-
类C
-
区分大小写,标识符可以字母、下划线(_)、美元符号($)开头
-
语句以分号(;)结尾:虽然不加分号也是被允许的,但会导致性能的降低,也易出现问题
-
严格模式(strict mode):对一些不规范写法进行处理,对不安全活动将抛出错误。
- 在脚本开头或函数体开头加上:"use strict";
function do (){ "use strict"; // 函数体 }
Variable
var
-
var定义的变量会成为包含它的函数的局部变量,函数退出时即被销毁,即声明的范围为函数作用域
- 可以通过省略var来定义一个全局变量,但这样做是不被推荐的;严格模式下,这样做会抛出ReferenceError异常
-
var的声明提升(hoist):使用var关键字声明的变量会自动提升到函数作用域的顶部【将所有变量声明拉到函数作用域顶部】
// 以下二者是等价的,均不会报错,输出undefined function foo() { console.log(age); // undefined var age = 20; } function foo(){ var age; console.log(age); // undefined age = 20; }
-
使用var反复多次声明同一个变量是被允许的
function foo(){ var age = 12; var age = 15; var age = 18; var age = 21; console.log(age); // 21 }
let
-
let与var作用类似,不同的是let声明的范围为块作用域
- 块作用域是函数作用域的子集,因此适用于var的作用域限制同样适用于let
if(true){ var name = "Leo"; console.log(name); // Leo } console.log(name); // Leo ------------------------------- if(true){ let name = "Leo"; console.log(name); // Leo } console.log(name); // ReferenceError: name is not defined
-
在同一个块作用域中,let不允许进行冗余声明是(SyntaxError)
- 声明冗余报错不会因混用let和var受到影响:这两个关键字声明的不是不同类型的变量,只是指出变量在相关作用域如何存在
var num = 1; console.log(num); // 1 { let num = 2; console.log(num); // 2 } let num = 1; console.log(num); { var num = 2; // SyntaxError: Identifier 'num' has already been declared console.log(num); }
-
暂时性死区:let声明的变量不会在作用域中被提升(hoist),必须先声明后引用
- let声明之前的执行瞬间称为“暂时性死区”,此阶段引用任何后面才声明的变量都会抛出ReferenceError
-
全局声明:let在全局作用域中声明的变量不会成为window对象的属性,而var声明的变量会
-
for循环中的let声明:
-
用var定义迭代变量会渗透到循环体外部,let定义迭代变量的作用域仅局限于for循环块内部
for(var i=0; i<5; i++){ // 循环逻辑 } console.log(i); // 5 -------------------------- for(let i=0; i<5; i++){ // 循环逻辑 } console.log(i); // ReferenceError: i is not defined
-
使用let声明迭代变量,JavaScript引擎在后台为每个迭代循环都声明一个新的迭代变量。
for(var i=0;i<5;i++){ setTimeout(()=>console.log(i),0); // 5 5 5 5 5 } --------------------------------------- for(let i=0;i<5;i++){ setTimeout(()=>console.log(i),0); // 0 1 2 3 4 }
-
const
-
与let行为基本相同
-
不允许重复声明,块声明作用域
-
区别:声明的同时必须初始化,切不可修改const声明的变量的值(TypeError)
- 此限制只适用于其指向的变量的引用(const变量引用的为一个对象时,修改const对象内部属性是被允许的)
- 不能用const声明迭代变量
-
Data Type
Undefined
-
使用var或let声明变量但未初始化时,变量会被赋予初始值undefined
-
具有值undefined的变量与未定义的变量是有区别的
-
赋值为undefined的变量是存在的,只是值为undefined;未定义的变量不存在,但对其typeof仍会返回undefined。二者是存在根本差别的
let message; // let age; //确保未被声明过 console.log(message); // undefined console.log(age); // ReferenceError: age is not defined console.log(typeof message); // undefined console.log(typeof age); // undefined
-
-
undefined是假值
Null
-
null表示一个空对象指针,typeof null会返回object
-
undefined由null派生而来,ECMA-262将他们定义为表面上相等
- undefined永远不必显示设置;但当变量需要保存一个对象且此时没有对象可以保存,就应当以null对变量进行填充,保持null的空对象指针语义,从而与undefined区别开来
-
null是假值
Boolean
-
转型函数:Boolean(param);
数据类型 转为true 转为false Boolean true false String 非空字符串 空字符串 Number 非零数值 0,NaN Object 任意对象 null Undefined \ undefined
Number
-
八进制字面量:以0o开头,后续数字均为0~7
- 后续数字一旦有超过0~7的范围,数字序列就被当做十进制解释
- 严格模式下,八进制字面量是无效的
-
十六进制字面量:以0x开头,后续数字为0~9和 a~f, 分别代表0~15
- a~f大小写均是被允许的
-
浮点值
-
ECMAScript会想方设法将值转换为整数值:小数点后面没有数字或只有0时,会被转换为整数进行处理
-
浮点值精度可达17位小数,但算数计算并不精准(IEEE 754数值造成的错误)
console.log( 0.1+0.2 == 0.3 ); // false 0.1+0.2得到结果为0.30000000000000004,出现舍入错误
-
-
数值的范围
- Number.MIN_VALUE(5e-324) ~ Number.MAX_VALUE(1.797693134862315e+308)
- 任何超出可表示范围的正负数会被表示为 ±Infinity
- 可以用函数 isFinite(param) 判断一个数是否在可表示范围内
-
NaN (Not a Number):表示本来要返回数值的操作失败了
-
任何涉及NaN的操作均返回NaN,NaN不等于包括NaN在内的任何值
-
利用 isNaN(param) 函数可以判断一个数是否“不是数值”:此函数会先将不是数值的值尝试转换成数值,任何不能转换为数值的值都会使这个函数返回true
console.log(isNaN(NaN)) // true console.log(isNaN(10)) // false console.log(isNaN("10")) // false 可转为数值10 console.log(isNaN("blue")) // true 不可转为数值 console.log(isNaN(true)) // false 可转为数值1
-
-
数值转换函数
-
Number(param):param可为任意数据类型
数据类型 转换规则 Boolean true转为1,false转为0 Number 直接返回 Null 0 Undefined NaN String 只包含数值字符,会被直接转换为数值(包括数字、正负号、小数点)
空字符串会转换为0
包含有效的十六进制格式(0x),会被由十六进制串转换为相对应的十进制数值Object 调用对象的valueOf( )方法,再按照上述规则转换返回值;若转换结果为NaN,则调用toString( )方法,按照转换字符串的规则转换 -
parseInt(param1,param2):param1为字符串,param2为进制
- 直接忽略最前面的空格,从第一个非空格字符串开始转换
- 第一个非空格字符不是数值字符、正负号,会立即返回NaN(意味着空字符串会返回NaN)
- 转换直到字符串尾或最后一个非数值字符
console.log(parseInt("1234hello")); // 1234 hello被忽略 console.log(parseInt("12.34")) // 12 .不是有效的整数字符 console.log(parseInt("")); // NaN console.log(parseInt("0xaf")); // 175 被解释为十六进制 console.log(parseInt("af",16)); // 175 被解释为十六进制
-
parseFloat(param):与parseInt原理类似
console.log(parseFloat("1234hello")); // 1234 hello被忽略 console.log(parseFloat("12.34.56")); // 12.34 console.log(parseFloat("0123.45")); // 123.45 console.log(parseFloat("3.125e7")); // 31250000
-
String
-
表示零或多个16位Unicode字符序列,通过字符串的length属性可以获取其长度
-
ECMAScript中的字符串时不可变的,一旦修改某个变量中的字符串值,必须先销毁原字符串再将新的值存入该变量中
-
向字符串转换
-
几乎所有值都有方法 toString( ),返回当前值的字符串等价物
-
Number,Boolean,Object,String均有toString( )方法,null与undefined没有toString( )方法
-
对Number调用toString( )方法时,可以传一个底数参数,表示以什么底数输出数值的字符串表示
-
-
String(param)转型函数
- param存在toString()方法则直接调用
- 值为null则返回"null",值为undefined则返回"undefined"
-
-
模板字面量:使用反引号,会保留字面量内的换行字符,以跨行定义字符串
-
字符串插值:模板字面量常用以支持字符串插值,即在一个连续定义中插入一个或多个值
-
字符串插值通过在 ${ } 中使用一个JavaScript表达式实现,插入的值会使用toString()强制转为字符串
let value = 19; let str = `<div> <a href="#"> <span>Leo is ${value} years old</span> </a> </div>`; console.log(str);
-
-
标签函数:自定义插值行为,前缀到模板字面量来应用自定义行为
- 第一个参数接收原始字符串被插值分割后形成的数组
- 由于插值表达式数量可变,应使用剩余操作符(...)将它们收集到一个数组中
let value = 19;
let exp = 'second';
function tagFunc(strings, ...exp){
console.log(strings);
console.log(exp);
}
tagFunc`${value} to the ${exp} power is ${value*value}`;
// [ '', ' to the ', ' power is ', '' ]
// [ 19, 'second', 361 ]
Symbol
ECMAScript6新增数据类型,确保对象属性使用唯一标识符,不会发生属性冲突的危险
-
Symbol(string):string为对符号的描述参数,与此符号的定义与标识完全无关
-
不能与new关键字一起作为构造函数使用,用以避免创建符号包装对象
let sym1 = Symbol('foo'); let sym2 = Symbol('foo'); console.log(sym1 == sym2); // false
-
全局符号注册表:运行时不同部分需要共享和重用符号实例时,以一个字符串作为键在全局符号注册表创建并重用符号
-
Symbol.for(string):使用某字符串调用时,检查全局运行时注册表,不存在相应符号则会创建一个新的符号实例,已存在则直接重用
-
即使采用相同的符号描述,全局注册表中定义的符号与Symbol()定义的符号也不等同
-
可以用Symbol.keyFor(param)查询全局注册表,param为符号实例,返回对应的描述字符串
let sym1 = Symbol.for('foo'); // 创建一个新的符号实例 let sym2 = Symbol.for('foo'); // 重用已有符号实例 console.log(sym1 === sym2); // true let sym3 = Symbol('foo'); console.log(sym1 === sym3); // false console.log(Symbol.keyFor(sym1)); // foo console.log(Symbol.keyFor(sym3)); // undefined
-
-
符号添加为对象属性
let s1 = Symbol('foo'), s2 = Symbol('bar'), s3 = Symbol('baz'), s4 = Symbol('qux'); let obj = { [s1]:'foo val' }; // o[s1] = 'foo val'; Object.defineProperty(obj, s2,{ value: 'bar val', enumerable: true}); Object.defineProperties(obj, { [s3] : {value:'baz val', enumerable: true}, [s4] : {value:'qux val', enumerable: true}, }); console.log(obj); //{ // [Symbol(foo)]: 'foo val', // [Symbol(bar)]: 'bar val', // [Symbol(baz)]: 'baz val', // [Symbol(qux)]: 'qux val' //}
-
获取对象的符号属性
let s1 = Symbol('foo'), s2 = Symbol('bar'); let obj = { [s1]: 'foo val', [s2]: 'bar val', baz: 'baz val', qux: 'qux val' }; console.log(Object.getOwnPropertySymbols(obj)); // [ Symbol(foo), Symbol(bar) ] console.log(Object.getOwnPropertyNames(obj)); // [ 'baz', 'qux' ] console.log(Object.getOwnPropertyDescriptors(obj)); //{ // baz: { value: 'baz val', writable: true, enumerable: true, configurable: true }, // qux: { value: 'qux val', writable: true, enumerable: true, configurable: true }, // [Symbol(foo)]: { value: 'foo val', writable: true, enumerable: true, configurable: true }, // [Symbol(bar)]: { value: 'bar val', writable: true, enumerable: true, configurable: true } //} console.log(Reflect.ownKeys(obj)); // [ 'baz', 'qux', Symbol(foo), Symbol(bar) ]
-
To be continued ......
Object
ECMAScript中的Object是所有对象的基类,任何对象都有这些属性与方法。每个Object实例都有如下属性与方法:
- constructor:创建当前对象的函数
- hasOwnProperty(propertyName):判断当前对象是否含有给定属性(String)
- propertyIsEnumerable(propertyName):判断当前属性是否可用for-in语句枚举
- toString():返回对象的字符串表示
- ......
Operator
一元操作符
-
递增/递减(++/--)
-
前缀时,先改变变量的值再对表达式求值;后缀时,先对表达式求值再改变变量的值
-
不限于整数,也可作用于以下类型的值
数值类型 规则 String 对有效数值形式则转为数值在改变,变量类型变为Number
对非有效数值形式,变量值变为NaN,变量类型变为NumberBoolean 将false转为0,将true转为1,再进行递增递减操作,变量类型变为Number float 直接+1或-1(注意:浮点数的加减可能不精确,出现舍入错误) Object 先调用对象的valueOf()方法,取到的值可操作则直接应用上述规则
如果为NaN,则调用toString()并再次应用其他规则
变量类型从Object变为Number
-
-
一元加减(+/-)
- 主要用于基本的算数操作,但也可以用于数据类型转换
- 对于数值,+/- 仅做正负的改变;对于非数值,会执行与Number()函数相同的改变,再根据+/- 改变正负
位操作符
- 按位 与(&) 或(|) 非(~) 异或(^)
- 左移(<<):按指定位数将数字的所有位左移(右侧补0,左侧符号位保留)
- 右移
- 有符号右移(>>):将所有的32位右移指定位,符号位保留(空位以符号位补充)
- 无符号右移(>>>):将所有的32位右移指定位,空位以0补充(对于正数无差别,对于负数有很大差异)
布尔操作符
-
逻辑非(!):现将操作数统一转为Boolean,再取反
- 可以用于将任意值转为Boolean型,与Boolean()转型函数等效:!!operand
-
逻辑与(&&):短路操作符,当第一个操作数为false时就不会对第二个操作数求值
- 可用于任何类型操作数,不一定会返回Boolean值
- 第一个操作数为Object,则返回第二个操作数;第二个操作数为Object,仅当第一个操作数求值为true才返回该Object
- 两个操作数均为Object,则返回第二个操作数
- 有一个操作数为NaN/undefined/null,直接返回NaN/undefined/null
- 可用于任何类型操作数,不一定会返回Boolean值
-
逻辑或(||):短路操作符,当第一个操作数为true时就不会对第二个操作数求值
-
可用于任何类型操作数,不一定会返回Boolean值
-
第一个操作数为Object,则返回第一个操作数;第一个操作数求值为false,则返回第二个操作数
-
两个操作数均为Object,则返回第一个操作数
-
两个操作数均为NaN/undefined/null,则返回NaN/undefined/null
-
-
可以避免给变量赋值null或undefined
let obj = preferredObj || backupObj;
- preferredObj为首选值,其不为null则会被赋给obj;backupObj为备用值,preferredObj为null时,其就会被赋给obj
-
乘性操作符
- 乘(*) 除(/) 取模(%)
- 注意对0,Infinity,NaN的处理
- 运算时,会对非数值的操作数进行类型转换
指数操作符
-
Math.pow(base,power):用于计算base的power次幂
-
ECMAScript 7新增指数操作符 ** ,与Math.pow函数等效
-
指数操作符同样支持复合赋值操作:**=
console.log(Math.pow(11,2)); // 121 console.log(11**2); // 121 console.log(121**0.5); // 11
-
加性操作符
-
加(+):计算数值时,需考虑Infinity,NaN,±0的情况
-
对两个字符串操作时,将第二个字符串拼到第一个后面;只有一个为字符串时,将另一个转为字符串后再拼在一起
-
任一操作数为Object时,会调用对象的valueOf()方法,由返回值参与加法运算;没有valueOf则先调用toString,得到的字符串再转为数值参与计算
let obj1 = { valueOf : function (){ return -1; }, }, obj2 = { valueOf : function (){ return "-2"; } }, obj3 = { valueOf : function (){ return -2; } }; console.log(obj1 + obj2); // -1-2 console.log(obj1 + obj3); // -3
-
-
减(-):计算数值时,需考虑Infinity,NaN,±0的情况
- 对字符串、布尔值、null、undefined,会调用Number()转为数值再计算
- 其他情况规则与加法相同
关系操作符
- 包括:>、<、>=、<=
- 操作数都是数值,执行数值比较
- 操作数都是字符串,则逐个比较字符串中对应字符的编码
- 有一个操作数是数值,则将另一个操作数转为数值,再做数值比较
- 任一操作数为Object,则调用其valueOf方法,对结果进行比较;若没有valueOf方法,则调用toString方法,对结果进行比较
- 对布尔值,将其转为数值再比较
- 涉及NaN的所有比较都会返回false
相等操作符
-
等于(==)和不等于(!=):对比较的两操作数先进行强制类型转换,再比较是否相等
- 任一操作数为Boolean,将其转为数值再比较是否相等
- String与Number比较,尝试将String转为Number,再比较是否相等
- Object和非Object比较,调用对象的valueOf方法获取其原始值,再比较是否相等
- null == undefined,且它们不能转换为其他类型的值进行比较
- 只要涉及NaN,立即返回false(NaN == NaN也返回false)
- 两个Object比较,仅当两操作数指向同一对象时返回true
-
全等(===)和不全等(!==):比较时不作任何强制类型转换
- null === undefined 返回false,因为他们是不同类型的值
条件操作符
- variable = boolean_expression ? true_value : false_value ;
逗号操作符
-
用于在一条语句中执行多个操作
let num1 = 1, num2 = 2, num3 = 3;
-
赋值时用逗号操作符分隔值,最终会返回表达式中最后一个值
let num = (5,4,3,2,1,0); // num = 0
Statement
for-in
-
用于枚举对象中的非符号键属性
-
若迭代的变量是null或undefined,则不执行循环体
for (const propName in window){ console.log(propName); }
-
for-of
-
用于遍历可迭代对象的元素
-
会按照可迭代对象的next( )方法产生值的顺序迭代元素
-
尝试迭代的变量不支持迭代时,语句会抛出错误
let arr = [1,2,3,4]; for (const num of arr){ console.log(num); }
-
break,continue,label
-
break语句用于立即退出当前循环,强制执行当前循环后的下一条语句
-
continue语句也立即退出当前循环,但会再次从循环顶部开始执行
-
label语句用于给语句加标签,可以通过break和continue语句引用(常用于嵌套循环):强制执行或强制退出标签所在循环
let num = 0; outermost: for (let i=0; i<10; i++){ for (let j=0; j<10; j++){ if (i===5 && j===5){ break outermost; } num ++; } } console.log(num); // 55 ------------------------------------- let num = 0; outermost: for (let i=0; i<10; i++){ for (let j=0; j<10; j++){ if (i===5 && j===5){ continue outermost; } num ++; } } console.log(num); // 95
with
-
用于将代码作用域设置为特定的对象,主要用于针对同一对象的反复操作
-
严格模式下,不允许with语句的使用
let obj = { value : 114514, str : 'fuck', name : 'obj' }; with (obj) { console.log(value); console.log(str); console.log(name); } //114514 //fuck //obj
-
switch
-
在每个case条件分支后,最好都加上break语句,否则代码会在匹配完当前条件后继续匹配下一条件
-
switch-case语句可以应用所有数据类型,条件的值也可以是变量或表达式
let str = 'hello world'; switch (str) { case 'hello'+' world': console.log('Greeting was found!'); // break; case 'goodbye': console.log('Closing was found'); break; default: console.log('Unexpected message was found'); }
-
在比较每个条件分支的值时使用'===',不会强制转换数据类型