变量有两种作用域:全局范围和函数内部范围
var globalVar; function foo() { /* 在这个位置也能输出localVar2,不过得到的结果会是undefined */ var localVar; if (globalVar > 0) { varl localVar2 = 2; } /* localVar2在这里使用就是不合法的了 */ }
变量的语句也可以在变量声明之前使用(变量提升),但是不推荐。
/* 下面这两个代码是等价的 */ function foo() { var x; x = 2; } function foo() { x = 2; var x; }
慎用全局变量
变量提升在局部变量使用时会引起混乱,比如在赋值前就访问了变量
建议在函数开始时声明所有的变量
检查相等和不相等的是===
和!==
,而不是==
和!=
最新的JavaScript标准ES6中引入了非提升的变量声明:let
和const
,某些环境下直接禁用var声明的方式
console.log('Val is:', val); /* 输出undifined */ ... for (var i = 0; i < 10; i++) { var val = 'different string'; /* 变量在函数之前提升 */ } /* --------------------------------------------------- */ console.log('Val is:', val); /* 语法错误 */ ... for (var i = 0; i < 10; i++) { let val = 'different string'; }
数字类型(number type)都是以浮点数的形式表示,对应的是C语言中的double
\(\text{MAX\_INT}=(2^{53}-1)=9007199254740991\)。
一些特殊值也属于数字类型,有:NaN
, Infinity
由于是浮点数,所以会有浮点数的坑,比如(0.1+0.2)==0.3
是false
但是对于位运算,结果确实32位的数字
字符串都是可变长度的,.length
属性返回字符串的长度。
字符串之间可以用+
来拼接。
除此之外,字符串还有许多有用的方法:
indexof(), charAt(), match(), search(), replace(), toUpperCase(), toLower(), slice(), substr(), ...
布尔值只有两种情况:true
false
JS中将值分为truthy和falsy,其中falsy值有: false
0
NaN
""
undefined
null
除此之外,其余都是truthy
undefined是指变量还没有赋值的状态
null是指希望返回一个值,但是无法返回正确的值的结果
要注意null和undefined二者并不相等
函数是可以提升的,也就是说可以先调用函数,再在后面定义。
函数可以返回多种类型,任何函数都会返回一个值,默认值是undefined
样例:
let aFuncVar = function (x) { console.log('Func called with', x); return x+1; }; myFunc(aFuncVar); function myFunc(routine) { // passed as a param console.log('Called with', routine.toString()); let retVal = routine(10); console.log('retVal', retVal); return retVal; }
输出结果:
Called with function (x) { console.log('Called with', x); return x+1; } Func called with 10 retVal 11
JavaScript JSON (w3school.com.cn)
Object类型是value:key
组成的称为对象(property)的无序集合
let foo = {}; let bar = {name: "Alice", age: 23, state: "California"};
其中name必须得是字符串,值可以是任何类型
访问:
bar.name
或bar["name"]
foo.nonExisent
==undefined
属性是可以后续添加的,比如:
let foo = {}; foo.name = "Fred";
属性也可以后续删除:
left foo = { name: "Fred" }; delete foo.name; /* foo现在为空 */
列举使用的keys:
Object.keys({name: "Alice", age: 23}) = ["name", "age"]
let anArr = [1,2,3];
数组的类型是object
,所以有typeof anArr == 'object'
一个数组里能存放多种不同的类型。
数组有的方法:length
push
pop
shift
unshift
sort
reverse
splice
filter
length
返回数组的长度
push
在数组最后插入一个值,并返回下标
pop
删除数组最后一个值,并返回删除的值
shift
删除首元素,然后将数组的所有元素左移
unshift
在数组头部插入新元素
sort
数组排序
reverse
数组翻转
splice(a, b, ...)
在数组中添加a个新元素,删除b个元素,后面参数是添加的新元素
concat
将两个数组合并后创建一个新数组
filter
传入一个检查函数,将满足检查条件的元素组成一个新数组返回ß
let date = new Date();
日期的类型是object
,有typeof date == 'object'
日期类型的原始数据是UNIX时间戳
方法:
valueOf()
返回时间戳toISOString()
以ISO时间格式返回日期2016-01-09T17:08:36.314ZtoLocalString()
返回类似1/9/2016, 9:08:36 AM格式的日期let re = /ab+c/
或let re2=new RegExp("ab+c")
字符串处理:
String: search(), match(), replace(), split() RegExp: exec(), test()
/re/.test(str)
检查字符串str
中是否有匹配re
的内容,返回true
orfalse
str.search(re)
返回字符串str
中匹配re
的子串的开头的下标,若不存在返回-1
re.exec(str)
返回str
中匹配re
的信息,自动往后匹配,直到返回null
str.match(re)
以数组的形式返回str
中所有匹配re
的子串
str.replace(re, s)
将str
中匹配re
的子串用s
来替换
try { notExistentFunction(); throw "Help!"; } catch (errstr) { // errstr === "Help!" console.log('Got exception', errstr); } finally { // This block is executed after try/catch }
嵌入单独的.js
文件
<script type="text/javascript" src="code.js"></script>
在HTML文件中嵌入
<script type="text/javascript"> //<![CDATA[ Javascript goes here... //]]> </script>
let obj = {count: 0}; obj.increment = function (amount) { this.count += amount; return this.count; }
上面的代码定义了一个类obj
,该类有一个字段count
,默认值为0;还有一个叫做increment
的方法。
对于一个类的方法,this
会绑定给该对象
let o = {oldProp: 'this is an old property'}; o.aMethod = function() { this.newProp = "this is a new property"; return Object.keys(this); // will contain 'newProp' } o.aMethod(); // will return ['oldProp','aMethod','newProp']
对于函数(不是类中的方法):
this
会绑定到全局的object
对象use strict
;this
会是undefined
函数也可以定义自己的属性,详情见下面的代码:
function plus1(value) { if (plus1.invocations == undefined) { plus1.invocations = 0; } plus1.invocations++; return value + 1; }
plus1.invocations
在上段代码中,代表的含义就是函数调用的次数函数也是一种对象,函数默认内置了一下的方法:
function func(arg) { console.log(this,arg); }
toString()
返回函数的代码,上面的样例中,返回的结果会是function func(arg) { console.log(this,arg); }
call(this, args)
指定this和传入的参数来调用该函数bind(this, args)
返回一个this和传入的参数绑定后的新函数绑定时,对于预留的参数可以用undefined
来预留,当调用新生成的函数时,会根据没有绑定的参数依次填入传入的值。
在JavaScript中,函数就是类(class),例如定义一个Rectangle
类:
function Rectangle(width, height) { this.width = width; this.height = height; this.area = function() { return this.width*this.height; } } let r = new Rectangle(26, 14); // {width: 26, height: 14} console.log(r) /* Rectangle { width: 26, height: 14, area: [Function] } */
用这种方式构造的函数也被称作生成器(constructor)`r.constructor.name == 'Rectangle'
JavaScript中每个对象都有一个对象原型(prototy)的概念。每个对象都有自己的原型,从而构成一个长链。
graph LR A(("Obj")) B(("Proto")) C(("Proto")) D(("null")) A====>B-...->C====>D访问JavaScript的一个对象时,当一个对象的属性不存在时,继续向它的原型搜索,直到返回null
。
但是在修改对象的一个属性时,如果当前对象不包含该属性,那么就直接创建这个属性,不会搜寻原型。
下面是一个使用原型的例子:
function Rectangle(width, height) { this.width = width; this.height = height; } Rectangle.prototype.area = function() { return this.width*this.height; } let r = new Rectangle(26, 14); // {width: 26, height: 14} let v = r.area(); // v == 26*14 Object.keys(r) == [ 'width', 'height' ] // own properties
改变对象的原型会引起所有实例的改变
假设我们有一个实例let r = new Rectangle(26, 14)
要对线面
r.newMethod = function() { console.log('New Method called'); } Rectangle.prototype.newMethod = function() { console.log('New Method called'); }
JavaScript的继承是面向原型的继承,支持单一继承和动态创建与修改。
所以原型赋值可以看作是在指定父类。
class Rectangle extends Shape { // 定义类和继承Shape constructor(height, width) { super(height, width); this.height = height; this.width = width; } area() { // 定义方法 return this.width * this.height; } static countRects() { // 定义静态方法 ... } }
JavaScript函数式编程的主要应用是匿名函数和lambda表达式。
下面的三个代码都是等价的:
/* code1 */ for (let i = 0; i < anArr.length; i++) { newArr[i] = anArr[i]*i; } /* code2 匿名函数 */ newArr = anArr.map(function (val, ind) { return val*ind; }); /* code3 lambda expression */ newArr = anArr.map((val, ind) => val*ind);
闭包(closure)在编程语言中是一种比较高级的概念
let globalVar = 1; function localFunc(argVar) { let localVar = 0; function embedFunc() {return ++localVar + argVar + globalVar;} return embedFunc; } let myFunc = localFunc(10);
myFunc闭包包含argVar, localVar和globalVar
闭包常用于在类中实现私有字段,例如:
let myObj = (function() { let privateProp1 = 1; let privateProp2 = "test"; let setPrivate1 = function(val1) { privateProp1 = val1;} let compute = function() {return privateProp1 + privateProp2;} return {compute: compute, setPrivate1: setPrivate1}; })(); typeof myObj; // 'object' Object.keys(myObj); // [ 'compute', 'setPrivate1' ] myObj.compute(); // return '1test'
我们需要注意在嵌入函数中使用this
:
'use strict'; function readFileMethod() { fs.readFile(this.fileName, function (err, data) { if (!err) { console.log(this.fileName, 'has length', data.length); } }); } let obj = {fileName: "aFile"; readFile: readFileMethod}; obj.readFile();
这段代码会产生错误,因为this
的值时undefined
,因为readFile
只是一个函数,并不是一个方法。
闭包在命令式的代码中也会产生棘手的问题:
// Read files './file0' and './file1' and return their length for (let fileNo = 0; fileNo < 2; fileNo++) { fs.readFile('./file' + fileNo, function (err, data) { if (!err) { console.log('file', fileNo, 'has length', data.length); } }); }
程序的运行结果是输出两个flie 2 has length
,这是为什么呢?
在执行fs.readFile
时,有两个参数,当一个参数是字符串,代表读取文件的名字,第二个参数是一个函数,当fs.readFile
结束之后,回调这个函数。
在fs.readFile
执行后,回调函数执行前,fileNo++
;当执行回调函数的时候,闭包内的fileNo
变成了2,所以最终的运行结果是输出两次2。
当我们把代码修改成下面的形式时:
for (let fileNo = 0; fileNo < 2; fileNo++) { var localFileNo = fileNo; fs.readFile('./file' + localFileNo, function (err, data) { if (!err) { console.log('file', localFileNo,'has length',data.length); } }); }
输出的localFileNo
会一直是1。
我们换一种思路来解决——用调用
的方式为fileNo
制作一个私有副本:
function printFileLength(aFileNo) { fs.readFile('./file' + aFileNo, function (err, data) { if (!err) { console.log('file', aFileNo, 'has length', data.length); } }); } for (let fileNo = 0; fileNo < 2; fileNo++) { printFileLength(fileNo); }
另一个思路——用let
来制作fileNo
的私有拷贝。
for (let fileNo = 0; fileNo < 2; fileNo++) { let localFileNo = fileNo; fs.readFile('./file' + localFileNo, function (err, data) { if (!err) { console.log('file', localFileNo, 'has length', data.length); } }); }
但是这两种方式都会有有时出现输出乱序的问题。
JavaScript JSON | 菜鸟教程 (runoob.com)
JSON的一些方法:
JSON.stringify(obj)
:以字符串的形式返回obj
的内容JSON.parse(s)
/* old */ var exportedName = (function () { var x, y, x; ... return {x: x, y: y}; })(); /* new */ var x, y, x; ... var exportedName = { x: x, y: y }; export exportedName;
/* old */ function myFunc(a, b) { a = a || 1; b = b || "Hello"; } /* new */ function myFunc(a = 1, b = "Hello") { }
/* old */ function myFunc() { var a = args[0]; var b = args[1]; var c = args[2]; ... } /* new */ function myFunc(a, b, ...theArgsArray) { var c = theArgsArray[0]; }
/* old */ function formatGreetings(name, age) { var str = "Hi " + name + " your age is " + age; /* new */ function formatGreetings(name, age) { let str = `Hi ${name} your age is ${age}`;
var a = [5, 6, 7] /* old */ for (var i = 0; i < a.length; i++) { console.log(a[i]); } /* new */ for (ent of a) { console.log(ent); }
数组、字符串、集合、哈希表都可以使用
obj?.prop /* 如果obj是undefined或者null就返回undefined 否则返回obj.prop */
BigInt - JavaScript | MDN (mozilla.org)