Stanford CS142: Web Applications Week2 JavaScript
基本语法
变量的作用域
变量有两种作用域:全局范围和函数内部范围
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
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
Object类型
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
}
在Web页面中使用JavaScript
-
嵌入单独的
.js
文件<script type="text/javascript" src="code.js"></script>
-
在HTML文件中嵌入
<script type="text/javascript"> //<![CDATA[ Javascript goes here... //]]> </script>
JavaScript的面向对象
定义类
let obj = {count: 0};
obj.increment = function (amount) {
this.count += amount;
return this.count;
}
上面的代码定义了一个类obj
,该类有一个字段count
,默认值为0;还有一个叫做increment
的方法。
this
对于一个类的方法,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)的概念。每个对象都有自己的原型,从而构成一个长链。
访问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的继承是面向原型的继承,支持单一继承和动态创建与修改。
所以原型赋值可以看作是在指定父类。
ECMAScript6的新内容
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);
}
});
}
但是这两种方式都会有有时出现输出乱序的问题。
JSON
JavaScript JSON | 菜鸟教程 (runoob.com)
JSON的一些方法:
JSON.stringify(obj)
:以字符串的形式返回obj
的内容JSON.parse(s)
JavaScript的一些新特性
导入全局变量
/* 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}`;
For of
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 */