声明
var
声明一个变量,可选初始化一个值
let
声明一个块作用域的局部变量,可选初始化一个值
const
声明一个块作用域的只读变量
var a;
console.log(a); //undefined
console.log(b); //undefined
var b;
console.log(c); //not defined
let x;
console.log(x); //undefined
console.log(y); //not defined
let y; //从此处向上叫代码的暂时性死区
var myArray = [];
if(!myArray[0]){ //undefined值在布尔类型环境中会被当做false
console.log('in'); //in
}
var a; //undefined值在数字类型环境中会被转换为NaN
a + 2; //NaN
var n = null; //null在数值类型环境中会被当做0,而布尔类型环境中会被当做false
console.log(n) //0
console.log(y); //not defined
let y = 5; //let(const)将不会提升变量到代码块的顶部
const PI = 3.14;
PI = 123; //Assignment to constant variable
const PI = []; //对象属性被赋值为常量是不受保护的
PI.push(1,2,3); //[1,2,3]
const PI = {}; //数组的被定义为常量也是不受保护的
PI.abc = 123; //{abc:123}
数据类型
ECMAScript标准定义了8种数据类型:
- 七种基本数据类型:
- 布尔值(Boolean),有2个值分别是
true
和false
. - null,一个表明null值得特殊关键字。JavaScript大小写敏感,因此
null
与Null
、NULL
或变体完全不同。 - undefined,和null一样是一个特殊得关键字,undefined表示变量定义时得属性。
- 数字(Number),整数或浮点数。
- 任意精度得证书(BigInt),可以安全地存储和操作大整数,甚至可以超过数字得安全整数限制。
- 字符串(String),字符串十一粗汉表示文本值得字符序列。
- 代表(Symbol)一种实例是唯一且不可改变得数据类型。
- 布尔值(Boolean),有2个值分别是
- 以及对象(Object)
var answer = 42;
answer = 'Thanks you'; //JavaScript是动态类型,这种赋值方式并不会提示出错
//包含得数字和字符串得表达式中使用 + ,JavaScript会把数字转换成字符串;涉及其它运算符(-)时,JavaScript不会把数字变为字符串。
//隐式转换
x = 'The answer is' + 42; //'The answer is 42'
'37' - 7; //30
'37' + 7; //377
//强制转换
parseInt('12.5'); //12
parseFloat('12.5'); //12.5
"1.1" + "1.1" = "1.11.1";
(+"1.1") + (+"1.1") = 2.2;
字面量
- 数组字面量(Array literals)
- 数字字面值是一个封闭在方括号对([])中包含有零个或多个表达式的列表,其中每个表达式代表数组的一元素。
- 布尔字面量(Boolean literals)
- 布尔类型有两种字面量:
true
和false
。
- 布尔类型有两种字面量:
- 浮点数字字面量(Floating-point literals)
- 组成部分
- 一个十进制整数,可以带正负号(+ -)
- 小数点(’.’)
- 小数部分(由一串十进制数表示)
- 指数部分
- 组成部分
- 整数(Integers)
- 整数可以用十进制、十六进制、八进制以及二进制表示。
- 对面字面量(Object literals)
- 对面字面值是封闭在花括号({})中的一个对象的零个或多个’属性名-值’对的(元素)列表。
- RegExp literals
- 字符串字面量(String literals)
//若在同一行中连写两个逗号(,),数组中就会产生一个没有被指定的元素,其初始值是undefined
var fish = ['Lion', ,'Angel']; //fish[1]是undefined
//尾部逗号可以减少向数组的最后添加元素时,因为忘记为这个最后一个元素加逗号 所造成的错误
var myList = ['home', , 'school', ];
-3.12e+12 // -3.12*10^12
//如果对象属性名字不是合法的JavaScript标识符,他必须用''包裹。
//属性的名字不合法,那么便不能用.访问属性值,而是通过类数组标记('[]')访问和赋值。
var unusualPropertyNames = {
"": "An empty string",
"!": "Bang!"
}
console.log(unusualPropertyNames.""); // 语法错误: Unexpected string
console.log(unusualPropertyNames[""]); // An empty string
console.log(unusualPropertyNames.!); // 语法错误: Unexpected token !
console.log(unusualPropertyNames["!"]); // Bang!
theProtoObj = {a:1,b:2};
handler = () => {console.log(123)};
var obj = {
__proto__: theProtoObj,handler,
// Methods
toString() {
// Super calls
return "d " + super.toString();
},
// Computed (dynamic) property names
[ 'prop_' + (() => 42)() ]: 42
};
console.log(obj); //{prop_42: 42, handler: ƒ, toString: ƒ}
console.log(obj.__proto__); //{a: 1, b: 2}
流程控制与错误处理
条件判断语句: if...else
和 switch
下面这些值将被计算出false
false
undefined
null
0
NaN
- 空字符串(
""
)
当传递给条件语句所其他的值,包括所有对象会被计算为真。
请不要混淆原始的布尔值 true
和 false
与 Boolean
对象的真和假
异常处理语句: trow
语句 和 try...catch
语句
如果 try
代码块没有抛出异常, catch
代码块就会被跳过。finally
代码块总会紧跟 try
和 catch
代码块之后执行,但会在 try
和 catch
代码块之后的其他代码之前执行。
function f() {
try {
console.log(0);
throw "bogus";
} catch(e) {
console.log(1);
return true; // this return statement is suspended
// until finally block has completed
console.log(2); // not reachable
} finally {
console.log(3);
return false; // overwrites the previous "return"
console.log(4); // not reachable
}
// "return false" is executed now
console.log(5); // not reachable
}
f(); // console 0, 1, 3; returns false
// Create an object type UserException
function UserException (message){
this.message=message;
this.name="UserException";
}
// Make the exception convert to a pretty string when used as
// a string (e.g. by the error console)
UserException.prototype.toString = function (){
return this.name + ': "' + this.message + '"';
}
function test(){
try{
console.log('查询数据库');
throw new UserException('访问无权限');
return 1;
}catch(err){
console.log('err');
return 2;
}finally{
console.log('关闭数据库连接');
return 3;
}
}
console.log(test()); //查询数据库 err 关闭数据库连接 3
function imgLoad(url) {
return new Promise(function(resolve, reject) {
var request = new XMLHttpRequest();
request.open('GET', url);
request.responseType = 'blob';
request.onload = function() {
if (request.status === 200) {
resolve(request.response);
} else {
reject(Error('Image didn\'t load successfully; error code:'
+ request.statusText));
}
};
request.onerror = function() {
reject(Error('There was a network error.'));
};
request.send();
});
}
imgLoad('').then(res=>{
console.log('success',res);
}).catch(err=>{
console.log('error',err);
})
循环和迭代
JavaScript中提供了这些循环语句:
- for 语句
- do…while 语句
- while 语句
- labeled 语句
- break 语句
- continue 语句
- for…in 语句
- 循环一个对象所有可枚举的属性
- for…of语句
- 在可迭代对象(包括
Array
、Map
、Set
、arguments
等等)上创建一个循环,对值得每一个独特属性调用一次迭代
- 在可迭代对象(包括
var num = 0;
for (var i = 0 ; i < 10 ; i++) { // i 循环
for (var j = 0 ; j < 10 ; j++) { // j 循环
if( i == 5 && j == 5 ) {
break; // i = 5,j = 5 时,会跳出 j 循环
} // 但 i 循环会继续执行,等于跳出之后又继续执行更多次 j 循环
num++;
}
}
alert(num); // 输出 95
var num = 0;
outPoint:
for (var i = 0 ; i < 10 ; i++){
for (var j = 0 ; j < 10 ; j++){
if( i == 5 && j == 5 ){
break outPoint; // 在 i = 5,j = 5 时,跳出所有循环,
// 返回到整个 outPoint 下方,继续执行
}
num++;
}
}
alert(num); // 输出 55
//使用continue语句,则可达到与未添加label相同的效果
var num = 0;
outPoint:
for(var i = 0; i < 10; i++) {
for(var j = 0; j < 10; j++) {
if(i == 5 && j == 5) {
continue outPoint;
}
num++;
}
}
alert(num); // 95
var object = {
a:1,
b:2,
} //a b
var object = [1,2,3]; //0 1 2 返回数组的索引
for(variable in object){
console.log(variable);
}
//for...in遍历结果是数组元素的下标
//for...of遍历结果是元素的值
let arr = [3, 5, 7];
arr.foo = "hello";
for (let i in arr) {
console.log(i); // 输出 "0", "1", "2", "foo"
}
for (let i of arr) {
console.log(i); // 输出 "3", "5", "7"
}
函数
function square(number){
retrurn number * number;
}
//函数表达式
var square2 = function square1(number){return number * number;}
console.log('square1',square1(3)); //报错
console.log('square2',square2(3)); //square2 9
function map(f, a){
var result = [];
var i;
for(i = 0; i != a.length; i++){
result[i] = f(a[i]);
}
return result;
}
var f = function(x){
return x * x * x;
}
numbers = [0,1,2,5,10];
var cube = map(f, numbers);
console.log(cube); // [0,1,8,125,1000]
函数可以被递归
function factorial(n){
if((n == 0) || (n == 1)){
return 1;
}else{
reuturn (n * factorial(n - 1));
}
}
作用域和函数堆栈
以下语句是等价的:
bar()
arguments.callee()
foo()
var foo = function bar(){
//statements go here
}
//获取树结构中所有的节点时,使用递归实现要容易得多
//递归函数使用了堆栈:函数堆栈
function walkTree(node){
if(node == null)
return;
if(node.childNodes){
for(var i = 0; i < node.childNodes.length; i++){
walkTree(node.childNodes[i]);
}
}
}
一个闭包必须保存它可见作用域中所有参数和变量。因为每一次调用传入的参数都可能不同,每一次对外部函数的调用实际上重新创建了一遍这个闭包。
function outside(x) {
function inside(y) {
return x + y;
}
return inside;
}
fn_inside = outside(3); // 可以这样想:给一个函数,使它的值加3
result = fn_inside(5); // returns 8
当同一个闭包作用域下两个参数或者变量同名时,就会产生命名冲突。更近的作用域有更高的优先权,所以最近的优先级最高,最远的优先级最低。
function outside() {
var x = 5;
function inside(x) {
return x * 2;
}
return inside;
}
outside()(10); // returns 20 instead of 10
一个闭包的函数定义了一个和外部函数的某个变量名称相同的变量,那么这个闭包将无法引用外部函数的这个变量。
事先不知道会需要将多少参数传递给函数时,可以使用arguments对象
function myConcat(separator) {
var result = ''; // 把值初始化成一个字符串,这样就可以用来保存字符串了!!
var i;
// iterate through arguments
for (i = 1; i < arguments.length; i++) {
result += arguments[i] + separator;
}
return result;
}
// returns "red, orange, blue, "
myConcat(", ", "red", "orange", "blue");
ES6开始,有两个新的类型的参数:默认参数,剩余参数
-
默认参数
//在JavaScript中,函数参数的的默认值时undefined //使用默认参数,在函数体的检查就不需要了 function multiply(a, b = 1) { b = (typeof b !== 'undefined') ? b : 1; return a*b; } multiply(5); // 5
-
剩余函数
//剩余参数语法允许将不确定数量的参数表示为数组 function multiply(multiplier, ...theArgs) { return theArgs.map(x => multiplier * x); } var arr = multiply(2, 1, 2, 3); console.log(arr); // [2, 4, 6]
箭头函数
var sayHi = ()=>console.log('Hello');
var sayHi = function(){console.log('Hello')};
var a = [
"Hydrogen",
"Helium",
"Lithium",
"Beryllium"
];
var a1 = a.map(function(s){ return s.length });
var a2 = a.map( s => s.length);
在箭头函数出现之前,每一个新函数都重新定义了自己的this值。
- 通过把this的值赋值给一个变量修复这个问题
- 创建一个约束函数(用bind绑定)使得this值被正确传递给
growUp()
函数 - 箭头函数捕捉闭包上下文this值,所以代码工作正常
function Person() {
//1. var self = this;
this.age = 0;
//3. setInterval(() => {
setInterval(function growUp() {
this.age++;
}, 1000);
//2. }.bind(this), 1000);
}
var p = new Person();
预定义函数
eval()
isNaN()
parseInt()
运算符和表达式
运算符
- 赋值运算符
- 比较运算符
- 算数运算符
- 位运算符
- 逻辑运算符
- 字符串运算符
- 三元运算符
- 逗号运算符
- 一元运算符
delete
typeof
void
- 关系运算符
in
instanceof
对于更复杂的赋值,解构赋值语法是一个能从数组或对象对应的数组结构或对象字面量里提取数据的JavaScript表达式
var foo = ["one", "two", "three"];
// 不使用解构
var one = foo[0];
var two = foo[1];
var three = foo[2];
// 使用解构
var [one, two, three] = foo;
短路求值
false
&& anything //被短路求值为falsetrue
|| anything //被短路求值为true
let a = true;
a && console.log('逻辑与:');
a || console.log('逻辑或');
typeof
操作符
var myFun = new Function("5 + 2");
var shape = "round";
var size = 1;
var today = new Date();
typeof myFun; // returns "function"
typeof shape; // returns "string"
typeof size; // returns "number"
typeof today; // returns "object"
typeof dontExist; // returns "undefined"
typeof true; // returns "boolean"
typeof null; // returns "object"
//对于属性值,typeof操作符将会返回属性所包含值的类型
typeof document.lastModified; // returns "string"
typeof window.length; // returns "number"
typeof Math.LN2; // returns "number"
//对于方法和函数
typeof blur; // returns "function"
typeof eval; // returns "function"
typeof parseInt; // returns "function"
typeof shape.split; // returns "function"
//对于预定义的对象
typeof Date; // returns "function"
typeof Function; // returns "function"
typeof Math; // returns "object"
typeof Option; // returns "function"
typeof String; // returns "function"
void
运算符,表明一个运算没有返回值
<a href="javascript:void(0)">Click here to do nothing</a>
in
操作符,如果所指定的属性确实在于所指定的对象中,则返回true
// Arrays
var trees = new Array("redwood", "bay", "cedar", "oak", "maple");
0 in trees; // returns true
3 in trees; // returns true
6 in trees; // returns false
"bay" in trees; // returns false (you must specify the index number,
// not the value at that index)
"length" in trees; // returns true (length is an Array property)
当你需要确认一个对象再运行时的类型,可以用instaceof
表达式
- 算数
- 字符串
- 逻辑值
- 基本表达式
- 左值表达式
new
super
class Person{
constructor(name){
this.name = name;
}
}
class Student extends Person{
constructor(name, age){
super(name);
this.age = age;
}
}
let stu = new Student('李四', 16);
console.log(stu); //Student {name: "李四", age: 16}
扩展语句符允许一个表达式原地展开
var a = [1,2,3];
var b = [4,5,...a,6];
console.log(b); //[4, 5, 1, 2, 3, 6]
function print(){
console.log(...arguments);
}
print('Hello',1,2,3); //Hello 1 2 3
数字和日期
数字的属性
Number.MIN_VALUE
Number.MAX_VALUE
数字的方法
Number.parseInt()
Number.isNaN()
Date对象的方法
- ‘set’ 方法,用于设置Date对象的日期和时间的值
- ‘get’ 方法,用于获取Date对象的日期和时间的值
- ‘to’ 方法,用于返回Date对象 的字符串格式的值
- parse 和 UTC 方法,用于解析Date字符串
function JSClock() {
var time = new Date();
var hour = time.getHours();
var minute = time.getMinutes();
var second = time.getSeconds();
var temp = "" + ((hour > 12) ? hour - 12 : hour);
if (hour == 0)
temp = "12";
temp += ((minute < 10) ? ":0" : ":") + minute;
temp += ((second < 10) ? ":0" : ":") + second;
temp += (hour >= 12) ? " P.M." : " A.M.";
return temp;
}
console.log(JSClock());
格式化字符串
应尽量使用String字面量,因为String对象的某些行为可能并不与直觉一致
var s1 = "2 + 2"; // Creates a string literal value
var s2 = new String("2 + 2"); // Creates a String object
eval(s1); // Returns the number 4
eval(s2); // Returns the string "2 + 2"
String对象方法
concat
连接连哥哥字符串并返回新的字符串
split
通过将字符串分离成一个个子串来把一个String对象分裂到一个字符串数组中
splice
从一个字符串提取片段并作为新字符串返回
substring
substr
分别通过指定起始和结束位置,起始位置和长度来返回字符串的指定子集
repeat
将字符串内容重复指定次数后返回
trim
去掉字符串开头和结尾的空白字符
多行模板字符串
模板字符串使用反勾号(``)包裹内容而不是单引号或双引号,模板字符串可以包含占位符
正则表达式
正则表达式是用于匹配字符串中字符组合的模式。在 JavaScript中,正则表达式也是对象。这些模式被用于RegExp
的 exec
和 test
方法,以及 String
的match
、 matchAll
、 replace
、 search
、 split
方法。
//正则表达式字面量构建
var re = /ab+c/;
//RegExp对象的构造函数构建
var re = new RegExp("ab+c");
正则表达式中的特殊字符
\
字符边界 /
转义字符
^
匹配输入的开始
$
匹配输入的结束
*
匹配前一个表达式0次或多次 {0,}
。
+
匹配前面一个表达式1次或多次 {1,}
。
?
匹配前面一个表达式0次或1次 {0,1}
.
匹配除换行符之外的任何单个字符
(x)
匹配’x’但不记住匹配项
(?:x)
匹配’x’但是不记住匹配项
x(?=y)
匹配’x’仅仅当’x’后面跟着’y’
x(?!y)
仅仅当’x’后面不跟着’y’时匹配’x’
(?<=y)x
匹配’x’仅仅当’x’前面是’y’
(?<!y)x
仅仅当’x’前面不是’y’时匹配’x’
x|y
匹配’x’或者’y’
{n}
匹配前面一个字符刚好出现了n次
{n,}
匹配前一个字符至少出现了n次
{n,m}
匹配前面的字符至少n次,至多m次
[xyz]
一个字符集合
[^xyz]
一个反向字符集,只要不是x y z
[\b]
匹配一个退格
\b
匹配一个词的边界
\B
匹配一个非单词边界
\d
匹配一个数字
\D
匹配一个非数字字符
\f
匹配一个换页符
\t
匹配一个水平制表符
\v
匹配一个垂直制表符
\r
匹配一个回车符
\n
匹配一个换行符
\s
匹配一个空白字符,包括空格、制表符、换页符和换行符
\S
匹配一个非空白字符
\w
匹配一个单字字符 等价于[A-Za-z0-9_]
\W
匹配一个非单字符
/\.jsx?$/.exec("App.jsx") //[".jsx", index: 3, input: "App.jsx", groups: undefined]
/\.jsx?$/.test("App.jsx") //true
/[abc][t]/.exec("ctApp") //["ct", index: 0, input: "ctApp", groups: undefined]
将用户输入转义为正则表达式中的一个字面字符串, 可以通过简单的替换来实现:
function escapeRegExp(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
//$&表示整个被匹配的字符串
}
使用插入语
"Capter 10.23".replace(/Capter (\d+)\.(\d*)/,"第$1章-第$2小节") //"第10章-第23小节"
使用正则表达表达式得方法
exec
一个在字符串中执行查找匹配的 RegExp 方法,它返回一个数组(未匹配到则返回null)
test
一个在字符串中测试是否匹配的 RegExp 方法,它返回true或false
match
一个在字符串中执行查找匹配的String方法,它返回一个数组(未匹配到则返回null)
matchAll
一个在字符串中执行查找所有匹配的String方法,它返回一个迭代器(iterator)
search
一个在字符串中测试匹配的String方法,它返回正则表达式在字符串中首次匹配项得索引,否则返回-1
replace
一个在字符串执行查找匹配的String方法,并且使用替换字符串匹配到的字符串
split
一个使用正则表达式或者一个固定字符串分割一个字符串,并将分割后得子字符存储到数组中得String方法
function testinput(re, str){
var midstring;
if (re.test(str)) {
midstring = " contains ";
} else {
midstring = " does not contain ";
}
console.log(str + midstring + re.source);
}
//设置全局标识得正则,连续执行test()方法,后续得执行将会从lastIndex处开始匹配字符串
var regex = /foo/g;
// regex.lastIndex is at 0
regex.test('foo'); // true
// regex.lastIndex is now at 3
regex.test('foo'); // false
var regObj = /abc/;
var str = 'hello,abc';
//两个结果一致
console.log(str.match(regObj)); //["abc", index: 6, input: "hello,abc", groups: undefined]
console.log(regObj.exec(str)); //["abc", index: 6, input: "hello,abc", groups: undefined]
//两个结果不一致
var regObj = /abc/g;
var str = 'heabcllo,abc';
console.log(str.match(regObj)); //(2) ["abc", "abc"]
console.log(regObj.exec(str)); //["abc", index: 2, input: "heabcllo,abc", groups: undefined]
var str = 'Capter 06.15';
console.log(str.replace(/(Capter).(\d+)\.(\d+)/,"第$2章-第$3节")) //第06章-第15节
var str = `
Capter 06.15
本章内容1
本章内容2
END
`
var result = str.split(/\s*\n/);
console.log(result.splice(1,result.length-2)); //(4) ["Capter 06.15", "本章内容1", "本章内容2", "END"]
正则表达式执行后的返回信息
当发生/d(b+)d/g使用两个不同状态的正则表达式对象,lastIndex 属性会得到不同的值。
var myRe = /d(b+)d/g;
var myArray = myRe.exec("cdbbdbsbz");
console.log("The value of lastIndex is " + myRe.lastIndex); //The value of lastIndex is 5
var myArray = /d(b+)d/g.exec("cdbbdbsbz");
console.log("The value of lastIndex is " + /d(b+)d/g.lastIndex); //The value of lastIndex is 0
使用括号的子字符串匹配
括号有记忆功能,回调这些括号中匹配的子串,使用数组元素[1]…[n]
var re = /(\w+)\s(\w+)/;
var str = "John Smith";
var newstr = str.replace(re, "$2, $1");
console.log(newstr); //Smith, John
通过标志进行高级搜索
g
全局搜索
i
不区分大小写搜索
m
多行搜索
s
允许.匹配换行符
u
使用Unicode码的模式进行匹配
y
执行“粘贴”搜索,匹配从目标字符串的当前位置开始,可以使用y标志
// 下面这个姓名字符串包含了多个空格和制表符,
// 且在姓和名之间可能有多个空格和制表符。
var names = "Orange Trump ;Fred Barney; Helen Rigby ; Bill Abel ; Chris Hand ";
var output = ["---------- Original String\n", names + "\n"];
// 准备两个模式的正则表达式放进数组里。
// 分割该字符串放进数组里。
// 匹配模式:匹配一个分号及紧接其前后所有可能出现的连续的不可见符号。
var pattern = /\s*;\s*/;
// 把通过上述匹配模式分割的字符串放进一个叫做nameList的数组里面。
var nameList = names.split(pattern);
// 新建一个匹配模式:匹配一个或多个连续的不可见字符及其前后紧接着由
// 一个或多个连续的基本拉丁字母表中的字母、数字和下划线组成的字符串
// 用一对圆括号来捕获该模式中的一部分匹配结果。
// 捕获的结果稍后会用到。
pattern = /(\w+)\s+(\w+)/;
// 新建一个数组 bySurnameList 用来临时存放正在处理的名字。
var bySurnameList = [];
// 输出 nameList 的元素并且把 nameList 里的名字
// 用逗号接空格的模式把姓和名分割开来然后存放进数组 bySurnameList 中。
//
// 下面的这个替换方法把 nameList 里的元素用 $2, $1 的模式
// (第二个捕获的匹配结果紧接着一个逗号一个空格然后紧接着第一个捕获的匹配结果)替换了
// 变量 $1 和变量 $2 是上面所捕获的匹配结果。
output.push("---------- After Split by Regular Expression");
var i, len;
for (i = 0, len = nameList.length; i < len; i++) {
output.push(nameList[i]);
bySurnameList[i] = nameList[i].replace(pattern, "$2, $1");
}
// 输出新的数组
output.push("---------- Names Reversed");
for (i = 0, len = bySurnameList.length; i < len; i++){
output.push(bySurnameList[i]);
}
// 根据姓来排序,然后输出排序后的数组。
bySurnameList.sort();
output.push("---------- Sorted");
for (i = 0, len = bySurnameList.length; i < len; i++){
output.push(bySurnameList[i]);
}
output.push("---------- End");
console.log(output.join("\n"));
包含非捕获括号 (?:
这个正则表达式寻找三个数字字符\d{3}
或者 |
一个左半括号\(
跟着三位数字\d{3}
, 跟着一个封闭括号 \)
, (结束非捕获括号 )
), 后跟着一个短破折号或正斜杠或小数点,随后跟随三个数字字符,当记忆字符 ([-\/\.])捕获并记住,后面跟着三位小数
\d{3},再后面跟随记住的破折号、正斜杠或小数点
\1,最后跟着四位小数
\d{4}。
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<meta http-equiv="Content-Script-Type" content="text/javascript">
<script type="text/javascript">
var re = /(?:\d{3}|\(\d{3}\))([-\/\.])\d{3}\1\d{4}/;
function testInfo(phoneInput) {
var OK = re.exec(phoneInput.value);
if (!OK)
window.alert(phoneInput.value + ' isn\'t a phone number with area code!');
else
window.alert('Thanks, your phone number is ' + OK[0]);
}
</script>
</head>
<body>
<p>Enter your phone number (with area code) and then click "Check".
<br>The expected format is like ###-###-####.</p>
<form action="#">
<input id="phone"><button onclick="testInfo(document.getElementById('phone'));">Check</button>
</form>
</body>
</html>
索引集合类
创建一个长度部位0,但是又没有任何元素的数组
//arrayLength
var arr = new Array(arrayLength);
var arr = Array(arrayLength);
// 这样有同样的效果
var arr = [];
arr.length = arrayLength;
for(const index in arr){}
for(const value of arr){}
填充数组
通过给元素赋值来填充数组 arr[0] = 'orange';
遍历数组
如果数组是非空数组,for...in
循环会把索引取出for...of
循环会把值取出
forEach()
遍历数组元素
数组的方法
concat()
连接两个数组
var myArray = new Array("1", "2", "3");
myArray = myArray.concat("a", "b", "c");
// myArray is now ["1", "2", "3", "a", "b", "c"]
join(deliminator = ',')
将数组的所有元素连接成一个字符串
var myArray = new Array("Wind", "Rain", "Fire");
var list = myArray.join(" - "); // list is "Wind - Rain - Fire"
push()
在数组末尾添加一个或多个元素,并返回数组操作后的长度
var myArray = new Array("1", "2");
myArray.push("3"); // myArray is now ["1", "2", "3"]
pop()
将数组移除最后一个元素,并返回该元素
var myArray = new Array("1", "2", "3");
var last = myArray.pop();
// myArray is now ["1", "2"], last = "3"
shift()
从数组移除第一个元素,并返回该元素
var myArray = new Array ("1", "2", "3");
var first = myArray.shift();
// myArray is now ["2", "3"], first is "1"
unshift()
从数组开头添加一个或多个元素,并返回数组的新长度
var myArray = new Array ("1", "2", "3");
myArray.unshift("4", "5");
// myArray becomes ["4", "5", "1", "2", "3"]
slice(start_index, upto_index)
从数组提取一个片段,并作为一个新数组返回
var myArray = new Array ("a", "b", "c", "d", "e");
myArray = myArray.slice(1, 4); // starts at index 1 and extracts all elements
// until index 3, returning [ "b", "c", "d"]
splice(index, count_to_remove, addElement1, addElement2, ...)
从数组移出一些元素,(可选)并替换它们
var myArray = new Array ("1", "2", "3", "4", "5");
myArray.splice(1, 3, "a", "b", "c", "d");
// myArray is now ["1", "a", "b", "c", "d", "5"]
// This code started at index one (or where the "2" was),
// removed 3 elements there, and then inserted all consecutive
// elements in its place.
reverse()
颠倒数组元素的顺序:第一个变成最后一个,最后一个变成第一个
var myArray = new Array ("1", "2", "3");
myArray.reverse();
// transposes the array so that myArray = [ "3", "2", "1" ]
sort()
给数组元素排序
var myArray = new Array("Wind", "Rain", "Fire");
myArray.sort();
// sorts the array so that myArray = [ "Fire", "Rain", "Wind" ]
indexOf(searchElement[, fromIndex])
在数组中搜索searchElement
并返回第一个匹配的索引
var a = ['a', 'b', 'a', 'b', 'a'];
console.log(a.indexOf('b')); // logs 1
// Now try again, starting from after the last match
console.log(a.indexOf('b', 2)); // logs 3
console.log(a.indexOf('z')); // logs -1, because 'z' was not found
lastIndexOf(searchElement[, fromIndex])
和 indexOf
差不多,但这是从结尾开始,并且是反向搜索
var a = ['a', 'b', 'c', 'd', 'a', 'b'];
console.log(a.lastIndexOf('b')); // logs 5
// Now try again, starting from before the last match
console.log(a.lastIndexOf('b', 4)); // logs 1
console.log(a.lastIndexOf('z')); // logs -1
forEach(callback[, thisObject])
在数组每个元素项上执行callback
var a = ['a', 'b', 'c'];
a.forEach(function(element) { console.log(element);} );
// a b c
map(callback[, thisObject])
在数组的每个单元项上执行callback函数,并把返回包含回调函数返回值的新数组
var a1 = ['a', 'b', 'c'];
var a2 = a1.map(function(item) { return item.toUpperCase(); });
console.log(a2); // logs A,B,C
filter(callback[, thisObject])
返回一个包含所有在回调函数上返回为true的元素的新数组
var a1 = ['a', 10, 'b', 20, 'c', 30];
var a2 = a1.filter(function(item) { return typeof item == 'number'; });
console.log(a2); // logs 10,20,30
every(callback[, thisObject])
当数组中每一个元素在callback上被返回true时就返回true
function isNumber(value){
return typeof value == 'number';
}
var a1 = [1, 2, 3];
console.log(a1.every(isNumber)); // logs true
var a2 = [1, '2', 3];
console.log(a2.every(isNumber)); // logs false
some(callback[, thisObject])
只要数组中有一项在callback上被返回true,就返回true
function isNumber(value){
return typeof value == 'number';
}
var a1 = [1, 2, 3];
console.log(a1.some(isNumber)); // logs true
var a2 = [1, '2', 3];
console.log(a2.some(isNumber)); // logs true
var a3 = ['1', '2', '3'];
console.log(a3.some(isNumber)); // logs false
reduce(callback[, initialValue])
使用回调函数 callback(firstValue, secondValue)
把数组列表计算成一个单一值
var a = [10, 20, 30];
var total = a.reduce(function(first, second) { return first + second; }, 0);
console.log(total) // Prints 60
多维数组
创建二维数组
var a = new Array(4);
for (i = 0; i < 4; i++) {
a[i] = new Array(4);
for (j = 0; j < 4; j++) {
a[i][j] = "[" + i + "," + j + "]";
}
}
console.log(a);
使用类数组对象
function printArguments() {
Array.prototype.forEach.call(arguments, function(item) {
console.log(item);
});
}
printArguments(1,2,3);
带键的集合
映射
Map对象,一个Map对象就是一个简单的键值对映射集合,可以按照数据插入时的顺序遍历所有元素
var sayings = new Map();
sayings.set('dog', 'woof');
sayings.set('cat', 'meow');
sayings.set('elephant', 'toot');
sayings.size; // 3
sayings.get('fox'); // undefined
sayings.has('bird'); // false
sayings.delete('dog');
sayings.has('dog'); // false
for (var [key, value] of sayings) {
console.log(key + ' goes ' + value);
}
// "cat goes meow"
// "elephant goes toot"
sayings.clear();
sayings.size; // 0
Object和Map的比较
一般地,objects会被用于将字符串类型映射到数值,Object允许设置键值对、根据键获取值、删除键、检测某个键是否存在
- Object的键均为Strings类型,在Map里键可以时任意类型
- 必须手动计算Object的尺寸,但是可以很容易地获取使用Map的尺寸
- Map的遍历遵循元素的插入顺序
- Object有原型,所以映射中有一些缺省的键(可以用
map = Object.create(null)
回避)
如何使用Map还是Object
- 如果键在运行时才能知道,或者所有的键类型相同,所有的值类型相同,那就使用Map
- 如果需要将原始值存储为键,则使用Map,因为Object将每个键视为字符串,不管它时一个数字值、布尔值还是任何其他原始值
- 如果需要对个别元素进行操作,使用Object
WeakMap对象
WeakMap对象是键值对的集合,它的键必须是对象类型。当其键所指对象没有其他地方引用时的时候,它会被GC回收掉
const privates = new WeakMap();
function Public() {
const me = {
// Private data goes here
name:'张三'
};
privates.set(this, me);
}
Public.prototype.method = function () {
const me = privates.get(this);
// Do stuff with private data in `me`...
console.log(me.name); //张三
};
module.exports = Public;
var pub = new Public();
pub.method();
Set对象
Set对象时一组值得集合,这些值时不重复得,可以按照添加顺序来遍历
var mySet = new Set();
mySet.add(1);
mySet.add("some text");
mySet.add("foo");
mySet.has(1); // true
mySet.delete("foo");
mySet.size; // 2
for (let item of mySet) console.log(item);
// 1
// "some text"
数组和集合的转换
Array.from(mySet);
[...mySet2];
mySet2 = new Set([1,2,3,4]);
Array和Set对比
- 数组中用于判断元素是否存在的
indeXOf
函数效率低下 - Set对象允许根据值删除元素,而数组中必须使用基于下标的
splice
方法 - 数组的
indexOf
方法无法找到NaN
值 - Set对象存储不重复的值,所以不需要手动处理包含重复值的情况
对象
var phone = new Object();
phone.name = '小米';
phone['color'] = '黑色';
phone['sendMsg'] = function(phoneNumber,msg){
console.log('往'+phoneNumber+':'+msg);
}
phone.sendMsg('11111111111','welcome');
for(var key in phone){
console.log(key); //name color sendMsg
}
console.log(Object.keys(phone)); //[ 'name', 'color', 'sendMsg' ]
console.log(Object.getOwnPropertyNames(phone)); //[ 'name', 'color', 'sendMsg' ]
枚举一个对象的所有属性
for...in
循环Object.keys(o)
Objcet.getOwnPropertyNames(o)
创建新对象
//1.使用对象初始化器
var obj = { property_1: value_1, // property_# 可以是一个标识符...
2: value_2, // 或一个数字...
["property" +3]: value_3, // 或一个可计算的key名...
// ...,
"property n": value_n }; // 或一个字符串
//2.使用构造函数
function Car(make, model, year) {
this.make = make;
this.model = model;
this.year = year;
}
var mycar = new Car("Eagle", "Talon TSi", 1993);
//3.使用Object.create方法,该方法可以为创建的对象选择其原型对象,而不用定义一个构造函数
// Animal properties and method encapsulation
var Animal = {
type: "Invertebrates", // Default value of properties
displayType : function() { // Method which will display type of Animal
console.log(this.type);
}
}
// Create new animal type called animal1
var animal1 = Object.create(Animal);
animal1.displayType(); // Output:Invertebrates
// Create new animal type called Fishes
var fish = Object.create(Animal);
fish.type = "Fishes";
fish.displayType(); // Output:Fishes
面向对象
基于类(Java)和基于原型(JavaScript)的对象系统的比较
基于类的(Java) | 基于原型的(JavaScript) |
---|---|
类和实例是不同的事物。 | 所有对象均为实例。 |
通过类定义来定义类;通过构造器方法来实例化类。 | 通过构造器函数来定义和创建一组对象。 |
通过 new 操作符创建单个对象。 | 相同。 |
通过类定义来定义现存类的子类,从而构建对象的层级结构。 | 指定一个对象作为原型并且与构造函数一起构建对象的层级结构 |
遵循类链继承属性。 | 遵循原型链继承属性。 |
类定义指定类的所有实例的所有属性。无法在运行时动态添加属性。 | 构造器函数或原型指定初始的属性集。允许动态地向单个的对象或者整个对象集中添加或移除属性。 |
创建层级结构
Employee
具有name
属性(默认值为空的字符串)和dept
属性(默认值为 “general”)。Manager
是Employee
的子类。它添加了reports
属性(默认值为空的数组,以Employee
对象数组作为它的值)。WorkerBee
是Employee
的子类。它添加了projects
属性(默认值为空的数组,以字符串数组作为它的值)。SalesPerson
是WorkerBee
的子类。它添加了quota
属性(其值默认为 100)。它还重载了dept
属性值为 “sales”,表明所有的销售人员都属于同一部门。Engineer
基于WorkerBee
。它添加了machine
属性(其值默认为空字符串)同时重载了dept
属性值为 “engineering”。
function Employee (name, dept) {
//短路用法
this.name = name || "";
this.dept = dept || "general";
}
function Manager(){
Employee.call(this);
this.reports = [];
}
Manager.prototype = Object.create(Employee.prototype);
Manager.prototype.constructor = Manager;
function WorkBee(name,dept,projs){
this.base = Employee;
this.base(name,dept);
// Employee.call(this,name,dept);
this.projects = projs || [];
}
WorkBee.prototype = Object.create(Employee.prototype);
WorkBee.prototype.constructor = WorkBee;
function SalesPerson(){
WorkBee.call(this);
this.dept = 'sales';
this.quota = 100;
}
SalesPerson.prototype = Object.create(WorkBee.prototype);
SalesPerson.prototype.constructor = SalesPerson;
function Engineer(name,projs,mach){
this.base = WorkBee;
this.base(name,'engineering',projs);
//继承的另一种途径是使用call()/apply()
//WorkBee.call(this,name,"engineering",projs);
this.machine = mach || '';
}
Engineer.prototype = Object.create(WorkBee.prototype)
Engineer.prototype.constructor = Engineer;
//必须显式的设置原型才能保持动态的继承,添加下面语句,`jane`对象的 `specialty` 为'none'了
Employee.prototype = new WorkBee;
var jane = new Engineer("Doe, Jane", ["navigator", "javascript"], "belau");
var sale = new SalesPerson();
//如果后续在`Emplyee` 或者 `WorkerBee` 原型中添加了属性,那些属性不会被`Engineer` 对象继承,对象`jane`不会继承`specialty`属性
Employee.prototype.specialty = 'none';
console.log(jane); //Engineer {base: [Function: Employee],name: 'Doe, Jane',dept: 'engineering',projects: [ 'navigator', 'javascript' ],machine: 'belau' }
console.log(sale); //SalesPerson {base: [Function: Employee],name: '',dept: 'sales',projects: [],quota: 100 }
-
new
操作符创建了一个新对象,并将其__proto__
属性设置为Engineer.prototype
。 -
new
操作符将该对象作为this
的值传递给Engineer
构造函数//1.创建一个空对象obj; //2.将该空对象的原型设置为构造函数的原型,即obj.__proto__ = func.prototype; //3.以该对象为上下文执行构造函数,即func.call(obj); //4,返回该对象,即rerurn obj。 function new2(func) { // func为某个构造函数 var createObject = Object.create(func.prototype); // 以构造函数的原型对象为原型,创建一个空对象,即创建一个{ __proto__: func.prototype } var returnObject = func.call(createObject); // 使用刚创建的空对象作为上下文(this)执行构造函数 if (typeof returnObject === 'object') { // 若构造函数有返回对象,则返回该对象 return returnObject; } else { // 若构造函数未返回对象,则返回Object.create创建的对象 return createObject; } };
在访问一个对象的属性时,JavaScript 将执行下面的步骤:
- 检查对象自身是否存在。如果存在,返回值。
- 如果本地值不存在,检查原型链(通过
__proto__
属性)。 - 如果原型链中的某个对象具有指定属性,则返回值。
- 如果这样的属性不存在,则对象没有该属性,返回 undefined。
定义instanceOf函数
function instanceOf(object, constructor) {
while (object != null) {
if (object == constructor.prototype)
return true;
if (typeof object == 'xml') {
return constructor.prototype == XML.prototype;
}
object = object.__proto__;
}
return false;
}
var chris = new Engineer("Pigman, Chris", ["jsd"], "fiji");
console.log(instanceOf (chris, Engineer)); //true
console.log(instanceOf (chris, Object)); //true
console.log(instanceOf (chris, SalesPerson)); //false
Promise
function wait(ms){
return new Promise(function(resolve){
setTimeout(function(){
resolve(ms);
},ms);
})
}
console.log('开始计时');
wait(2000).then((delayMs)=>{
console.log(`已经过了${delayMs}ms`);
})
不同于“老式”的传入回调,有以下约定
- 在本轮JavaScript event loop(事件循环)运行完成之前,回调函数时不会被调用的
- 通过
then()
添加的回调函数总会被调用,即便它是在异步操作完成之后才被添加的函数 - 通过多次调用
then()
,可以添加多个回调函数,它们会按照插入顺序一个接一个独立执行
因此,Promise最直接的好处就是链式调用(chaining)
Promise的三种状态
- pending:初始状态,既不是成功,也不是失败状态
- fulfilled:意味着操作成功完成
- rejected:意味着操作失败
function wait(ms){
return new Promise(function(resolve){
setTimeout(function(){
resolve(ms);
},ms);
})
}
console.log('开始计时');
wait(1000)
.then((ms)=>{
console.log(`1已等待了${ms}ms`);
throw new Error("我异常了")
})
.catch(err=>{
console.log("捕获异常 err",err);
return wait(1000);
})
.then((ms)=>{
console.log(`2已等待了${ms}ms`);
return wait(ms);
})
.then((ms)=>{
console.log(`3已等待了${ms}ms`);
return wait(ms);
})
.then((ms)=>{
console.log(`4已等待了${ms}ms`);
return wait(ms);
})
function waiCallback(ms,callback){
setTimeout(function(){
callback(ms)
},ms);
}
console.log('开始计时');
waiCallback(1000,function(ms){
console.log(`1已等待了${ms}ms`);
waiCallback(ms,function(ms){
console.log(`2已等待了${ms}ms`);
waiCallback(ms,function(ms){
console.log(`3已等待了${ms}ms`);
waiCallback(ms,function(ms){
console.log(`4已等待了${ms}ms`);
})
})
})
})
因为Promise.prototype.then
和 Promise.prototype.catch
方法返回promise对象,所以它们可以被链式调用
Promise.all(iterable)
Promise.race(iterable)
Promise.reject(reason)
Promise.resolve(value)
ECMAScript2017 新增async/await
function wait(ms){
return new Promise(function(resolve){
setTimeout(function(){
resolve(ms);
},ms);
})
}
function failureCallback(error) {
console.log("失败: " + error);
}
console.log('开始计时');
async function run(){
try{
let ms = await wait(1000)
console.log(`1已等待${ms}ms`);
ms = await wait(ms)
console.log(`2已等待${ms}ms`);
ms = await wait(ms)
console.log(`3已等待${ms}ms`);
ms = await wait(ms)
console.log(`4已等待${ms}ms`);
}catch(error){
failureCallback(error)
}
}
run();
function wait(){
return new Promise(function(resolve){
setTimeout(function(){
resolve(1000);
},1000);
})
}
var co = function(cb){
return function(){
return new Promise(function(resolve){
cb();
resolve();
})
}
}
var func1 = function(){
console.log("call func1");
}
var func2 = function(){
console.log("call func2");
}
Promise.resolve().
then(wait).
then(func1).
then(wait).
then(func2)
//返回一个Promise实例,如果参数中promise有一个失败(rejected),此实例回调失败,会立刻进入catch状态
Promise.all([func1,func2]).then(function(values){
console.log('success->',values);
}).catch((func1,func2)=>{
console.log('error->',func1,func2);
})
//返回一个Promise实例,一旦迭代器中某个promise解决或拒绝,返回promise就会解决或拒绝
Promise.race([func1,func2]).then(function(values){
console.log('success->',values);
}).catch((func1,func2)=>{
console.log('error->',func1,func2);
})
//实现时序组合
[wait,func1,func2].reduce((p,f) => p.then(f),Promise.resolve())
//时间组合可以通过async/await
//使用async/await时,最常见的错误就是忘记了await关键字
async function test(){
for(let f of [wait,func1,wait,func2]){
await f();
}
}
test()
迭代器和生成器
迭代器是通过使用next()
方法实现Iterator protocol的任何一个对象,该方法返回具有两个属性的对象:value
,这个是序列中的next值;和done
,如果已经迭代到序列中的最后一个值,则它为true
。如果value
和done
一起存在,则它是迭代器的返回值。
function makeInterator(array){
let nextIndex = 0;
return{
next:function(){
return nextIndex < array.length ? {value:array[nextIndex++],done:false} : {done:true}
// let result;
// if(nextIndex < array.length){
// result = {value:array[nextIndex],done:false}
// nextIndex++
// }else{
// result = {done:true}
// }
// return result
}
}
}
let it = makeInterator([1,2,3])
let result;
while(!(result = it.next()).done){
console.log(result);
}
虽然自定义的迭代器是一个有用的工具,但由于需要显示地维护其内部状态,因此需要谨慎地创建。生成器函数提供一个强大的选择:它允许你定义一个包含自由迭代算法的函数,同时它可以自动维护自己的状态。生成器函数使用 function* 语法编写
function* idMaker(maxCount){
var index = 0;
while(index <= maxCount){
yield index++;
}
}
var gen = idMaker(3)
var result;
while( !(result=gen.next()).done){
console.log(result);
}
String
、Array
、TypedArray
、Map
和 Set
都是内置可迭代对象,因为它们原型对象都拥有一个Symbol.iterator
方法
var myIt = {};
myIt[Symbol.iterator] = function* (){
yield 1;
yield 2;
yield 3;
}
for(let value of myIt){
console.log(value);
}
a = new Map();
a.set('a',1);
a.set('b',2);
a.set('c',3);
var getMap = a[Symbol.iterator]()
console.log(getMap.next());
console.log(getMap.next());
console.log(getMap.next());
元编程
ECMAScript2015 开始,JavaScript获得了Proxy和Reflect对象的支持,允许你拦截并定义基本语言操作的自定义行为(例如:属性查找、赋值、枚举、函数调用等)
let handler = {
get:function(target,name){
console.log('target:',target);
console.log('name:',name);
if(target.hasOwnProperty(name)){
return target[name]
}else{
console.log('没有找到你要的属性'+name);
return
}
}
};
var p = new Proxy({},handler);
p.a = 1;
console.log(p.a);
console.log(p.abc);
术语
handler
包含陷阱的占位符对象traps
提供属性访问的方法。这类似于操作系统中陷阱的概念target
代理虚拟化的对象、它通常用作代理的存储后端。根据目标验证关于对象不可扩展性或不可配置属性的不变量invariants
实现自定义操作时保持不变的语义称为不变量。如果你违反处理程序的不变量,则会抛出一个TypeError
句柄和陷阱
let handler = {
get:function(target,name){
// console.log('target:',target);
// console.log('name:',name);
if(target.hasOwnProperty(name)){
return target[name]
}else{
console.warn('没有找到你要的属性'+name);
return 'error'
}
},
set:function(target,name,value){
// console.log('set:',target,name,value);
target[name] = '#' +value;
},
deleteProperty:function(target,name){
// console.log('delete',target,name);
console.log(target,'删除属性',name); //{ a: '#a', abc: '#abc' } '删除属性' 'abc'
delete target[name]
},
};
var p = new Proxy({},handler);
p.a = 'a';
p.abc = 'abc'
delete p.abc
console.log(p.a); //#a
console.log(p.abc); //没有找到你要的属性abc error
撤销Proxy
Proxy.revocable()
方法被用来创建可撤销的Proxy对象。这意味着proxy可以通过revoke函数来撤销,并且关闭代理。此后,代理上任意的操作都会导致TypeError
var revocable = Proxy.revocable({}, {
get: function(target, name) {
return "[[" + name + "]]";
}
});
var proxy = revocable.proxy;
console.log(proxy.foo); // "[[foo]]"
revocable.revoke();
console.log(proxy.foo); // TypeError is thrown
proxy.foo = 1 // TypeError again
delete proxy.foo; // still TypeError
typeof proxy // "object", typeof doesn't trigger any trap
反射
Reflect
是一个内置对象,它提供可拦截JavaScript操作的方法
console.log(Reflect.has(Object,'toString')); //true
在ES5,我们通常使用Function.prototype.apply()
方法调用一个具有给定this值和arguments数组(或类数据对象)的函数
console.log(Function.prototype.apply.call(Math.floor,undefined,[1.75])); //1
console.log(Reflect.apply(Math.floor,undefined,[1.75])); //1
console.log(Reflect.apply(''.charAt, 'ponies', [3])); //i
检查属性定义是否成功
使用Object.defineProperty
,如果成功返回一个对象,否则抛出一个TypeError,你将使用try…catch块来捕获定义属性时发生的任何错误。因为Reflect.defineProperty
返回一个布尔值表示的成功状态,你可以在这里使用if…else块:
if (Reflect.defineProperty(target, property, attributes)) {
// success
} else {
// failure
}
严格模式
严格模式对正常的 JavaScript语义做了一些更改
- 严格模式通过抛出错误来消除一些原有静默错误
- 严格模式修复了一些导致JavaScript引擎难以执行优化的缺陷:有时候,相同的代码,严格模式可以比非严格模式下运行得更快
- 严格模式禁用了在ECMAScript的未来版本中可能会定义的一些语法
// 整个脚本都开启严格模式的语法
"use strict";
var v = "Hi! I'm a strict mode script!";
function strict() {
// 函数级别严格模式语法
'use strict';
function nested() {
return "And so am I!";
}
return "Hi! I'm a strict mode function! " + nested();
}
将过失错误转成异常
-
严格模式,无法再意外创建全局变量。在普通的JavaScript里面给一个错误命名的变量名赋值会使全局对象新增一个属性并继续"工作"(尽管将来可能会失败:在现代的JavaScript中有可能)。严格模式中意外创建全局变量被抛出错误替代:
"use strict"; // 假如有一个全局变量叫做mistypedVariable mistypedVaraible = 17; // 因为变量名拼写错误 // 这一行代码就会抛出 ReferenceError
-
严格模式,会使引起静默失败(silently fail,注:不报错也没有任何效果)的赋值操作抛出异常。例如,
NaN
是一个不可写的全局变量。在正常模式下,给NaN
赋值不会产生任何作用;开发者也不会受到任何错误反馈。 但在严格模式下, 给NaN
赋值会抛出一个异常。任何在正常模式下引起静默失败的赋值操作(给不可写属性赋值,给只读属性(getter-only)赋值,给不可扩展对象(non-extensible object)的新属性) Object构造函数的方法"use strict"; // 给不可写属性赋值 var obj1 = {}; Object.defineProperty(obj1, "x", { value: 42, writable: false }); obj1.x = 9; // 抛出TypeError错误 // 给只读属性赋值 var obj2 = { get x() { return 17; } }; obj2.x = 5; // 抛出TypeError错误 // 给不可扩展对象的新属性赋值 var fixed = {}; Object.preventExtensions(fixed); fixed.newProp = "ohai"; // 抛出TypeError错误
-
严格模式,试图删除不可删除的属性时会抛出异常
"use strict"; delete Object.prototype; // 抛出TypeError错误
-
严格模式,要求函数的参数名唯一。在正常模式下,最后一个重名参数会被遮盖之前的重名参数。之前的参数仍然可以通过
arguments[i]
来访问,还不是完全访问。function sum(a, a, c) { // !!! 语法错误 "use strict"; return a + a + c; // 代码运行到这里会出错 }
-
严格模式,禁止八进制数字语法。ECMAScript并不包含八进制语法,但所有的浏览器都支持这种以0开头的八进制语法:
0644 === 420
还有"\045"==="%"
。在ECMAScript6中支持一个数据加"0o" 的前缀来表示八进制数"use strict"; var sum = 015 // !!! 语法错误
-
ECMAScript6 的严格模式禁止设置primitive值的属性。不采用严格模式,设置属性将会简单忽略(no-op),采用严格模式,将抛出TypeError错误
primitive(基本数据类型):string,number,bigint,boolean,null,undefined,symbol
(function() { "use strict"; false.true = ""; //TypeError (14).sailing = "home"; //TypeError "with".you = "far away"; //TypeError })();
-
严格模式禁止删除声明变量
"use strict"; var x; delete x; // !!! 语法错误 eval("var y; delete y;"); // !!! 语法错误
-
名称eval和arguments不能通过程序语法被绑定(be bound)或赋值,以下所有尝试将引起语法错误
"use strict"; eval = 17; arguments++; ++eval; var obj = { set p(arguments) { } }; var eval; try { } catch (arguments) { } function x(eval) { } function arguments() { } var y = function eval() { }; var f = new Function("arguments", "'use strict'; return 17;");
-
不再支持
arguments.callee
。正常模式下,arguments.callee
指向当前正在执行的函数。这个作用很小:直接给执行函数命名就可以了!此外,arguments.callee
十分不利于优化,例如内联函数,因为arguments.callee
会依赖对非内联函数的引用。在严格模式下,arguments.callee
是一个不可删除属性,而且赋值和读取时都会抛出异常:"use strict"; var f = function() { return arguments.callee; }; f(); // 抛出类型错误
内存管理
像C语言这样的底层语言一般都有底层的内存管理接口,比如malloc()
和 free()
。相反,JavaScript是在创建变量(对象,字符串等)时自动进行了分配内存,并且在不使用它们时“自动”释放,释放的过程称为垃圾回收
内存生命周期
- 分配你所需要的内存
- 使用分配到的内存(读、写)
- 不需要时将其释放/归还
所有语言第二部分都是明确的。第一和第三部分在底层语言中是明确的,但在像JavaScript这些高级语言中,大部分是隐含的。
heap snapshot 堆快照
allocation instrumentation on timeline 时间表上的分配工具
allcation sampling 全方位采样
function gen(){
var a = '';
return{
reset:function(){
a = '';
},
grow:function(){
return a += new Array(10000).join('*')
}
}
}
var a = gen();
a.grow(); //内存涨
a.grow(); //内存涨
a.grow(); //内存涨
a.reset(); //内存降
垃圾回收算法,主要依赖于引用的概念。在内存管理的环境中,一个对象如果有访问另一个对象的权限(隐式或者显示),叫做一个对象引用另一个对象。例如,一个JavaScript对象具有对它原型的引用(隐式引用)和对它属性的引用(显示引用)
在这里,“对象”的概念不仅特指JavaScript对象,还包括函数作用域(或者全局词法作用域)
var o = {
a: {
b:2
}
};
// 两个对象被创建,一个作为另一个的属性被引用,另一个被分配给变量o
// 很显然,没有一个可以被垃圾收集
var o2 = o; // o2变量是第二个对“这个对象”的引用
o = 1; // 现在,“这个对象”只有一个o2变量的引用了,“这个对象”的原始引用o已经没有
var oa = o2.a; // 引用“这个对象”的a属性
// 现在,“这个对象”有两个引用了,一个是o2,一个是oa
o2 = "yo"; // 虽然最初的对象现在已经是零引用了,可以被垃圾回收了
// 但是它的属性a的对象还在被oa引用,所以还不能回收
oa = null; // a属性的那个对象现在也是零引用了
// 它可以被垃圾回收了
该算法有个限制:无法处理循环引用的示例。在下面的例子中,两个对象被创建,并且相互引用,形成了一个循环。它们被调用后会离开函数作用域,所以它们已经没有用了,可以被回收了。然而,引用计数算法考虑到它们互相都有至少一次引用,所以它们不会被回收。
function f(){
var o = {};
var o2 = {};
o.a = o2; // o 引用 o2
o2.a = o; // o2 引用 o
return "azerty";
}
f();
var div;
window.onload = function(){
div = document.getElementById("myDivElement");
div.circularReference = div;
div.lotsOfData = new Array(10000).join("*");
};
标记-清除算法,该算法假定一个叫做根(root)的对象(在JavaScript里,根是全局对象)。垃圾回收器将定期从根开始,找所有从根开始引用的对象,然后找这些对象引用的对象…从根开始,垃圾回收器将找到所有可以获得的对象和收集所有不能获得的对象
这个算法比前一个要好,因为“有零引用的对象”总是不可获得的,但是相反却不一定,参考“循环引用”
在上面的示例中,函数调用返回之后,两个对象从全局对象出发无法获取。因此,他们将会被垃圾回收器回收。第二个示例同样,一旦div和其事件处理无法从根获取到,他们将会被垃圾回收器回收
并发模型和事件循环
栈
函数调用形成了一个栈帧
function foo(b) {
var a = 10;
return a + b + 11;
}
function bar(x) {
var y = 3;
return foo(x * y);
}
console.log(bar(7)); // 返回 42
当调用 bar
时,创建了第一个帧 ,帧中包含了 bar
的参数和局部变量。当 bar
调用 foo
时,第二个帧就被创建,并被压到第一个帧之上,帧中包含了 foo
的参数和局部变量。当 foo
返回时,最上层的帧就被弹出栈(剩下 bar
函数的调用帧 )。当 bar
返回的时候,栈就空了。
堆
对象被分配在一个堆中,即用以表示一大块非结构化的内存区域
队列
一个 JavaScript 运行时包含了一个待处理的消息队列。每一个消息都关联着一个用以处理这个消息的函数。
在事件循环期间的某个时刻,运行时从最先进入队列的消息开始处理队列中的消息。为此,这个消息会被移出队列,并作为输入参数调用与之关联的函数。正如前面所提到的,调用一个函数总是会为其创造一个新的栈帧。
函数的处理会一直进行到执行栈再次为空为止;然后事件循环将会处理队列中的下一个消息(如果还有的话)
事件循环
while (queue.waitForMessage()) {
queue.processNextMessage();
}
函数 setTimeout
接受两个参数:待加入队列的消息和一个延迟(可选,默认为 0)。这个延迟代表了消息被实际加入到队列的最小延迟时间。如果队列中没有其它消息,在这段延迟时间过去之后,消息会被马上处理。但是,如果有其它消息,setTimeout
消息必须等待其它消息处理完。因此第二个参数仅仅表示最少延迟时间,而非确切的等待时间。
const s = new Date().getSeconds();
setTimeout(function() {
// 输出 "2",表示回调函数并没有在 500 毫秒之后立即执行
console.log("Ran after " + (new Date().getSeconds() - s) + " seconds");
}, 500);
while(true) {
if(new Date().getSeconds() - s >= 2) {
console.log("Good, looped for 2 seconds");
break;
}
}
JavaScript中的相等性判断
ES2015中有四种相等算法:
- 非严格相等比较(==)
- 严格相等比较(===):用于
Array.prototype.indexOf
、Array.prototype.lastIndexOf
、和case-matching
- 同值零:用于
%TypedArray%
和ArrayBuffer
构造函数、以及Map和Set操作,并将用于ES2016/ES7 中的String.prototype.includes
- 同值:用于所有其他地方
JavaScript提供三种不同的值比较操作:
- 严格相等(“triple equals” 或“identity”),使用===
- 宽松相等(“double equals”),使用==
- 以及
Object.is
(ECMAScript2015/ES6新特性)
这三个运算符的原语中,没有一个会比较两个变量是否结构上概念类似。对于任意两个不同的非原始对象,即便他们有相同的结构,以上三个运算符都会计算得到false
严格相等
全等操作符比较两个值是否相等,两个被比较的值在比较前都不进行隐式转换。如果两个被比较的值具有不同的类型,这两个值是不全等的。否则,如果两个被比较的值类型相同,值也相同,并且都不是 number 类型时,两个值全等。
非严格相等==
列是 被比较值A 行是 被比较值B
Undefined | Null | Number | String | Boolean | Object | |
---|---|---|---|---|---|---|
Undefined | true | true | false | false | false | IsFalsy(B) |
Null | true | true | false | false | false | IsFalsy(B) |
Number | false | false | A === B | A === ToNumber(B) | A=== ToNumber(B) | A== ToPrimitive(B) |
String | false | false | ToNumber(A) === B | A === B | ToNumber(A) === ToNumber(B) | ToPrimitive(B) == A |
Boolean | false | false | ToNumber(A) === B | ToNumber(A) === ToNumber(B) | A === B | ToNumber(A) == ToPrimitive(B) |
Object | false | false | ToPrimitive(A) == B | ToPrimitive(A) == B | ToPrimitive(A) == ToNumber(B) | A === B |
ToPrimitive(A)
通过尝试调用A的A.toString
和 A.valueOf
方法,将参数A转换为原始值(Primitive)
isFalsy(A)
方法的值为true ,当且仅当A效仿undefined
null == undefined //true
0 == false //true
1 == true //true
[] == 0 //true ==> Number([].toString)
[] == false //true
Boolean(0) //false
Boolean('0') //true
null == false //false
undefined == false //false
0 == null //false
0 == NaN //false
0 == undefined //false
NaN == NaN //false
相等比较的模型
x | y | == | === | Object.is |
---|---|---|---|---|
undefined | undefined | true | true | true |
null | null | true | true | true |
true | true | true | true | true |
false | false | true | true | true |
"foo" | "foo" | true | true | true |
0 | 0 | true | true | true |
+0 | -0 | true | true | false |
0 | false | true | false | false |
"" | false | true | false | false |
"" | 0 | true | false | false |
"0" | 0 | true | false | false |
"17" | 17 | true | false | false |
[1,2] | "1,2" | true | false | false |
new String("foo") | "foo" | true | false | false |
null | undefined | true | false | false |
null | false | false | false | false |
undefined | false | false | false | false |
{ foo: "bar" } | { foo: "bar" } | false | false | false |
new String("foo") | new String("foo") | false | false | false |
0 | null | false | false | false |
0 | NaN | false | false | false |
"foo" | NaN | false | false | false |
NaN | NaN | false | false | true |