Loading

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;
}

注意事项

  1. 慎用全局变量

  2. 变量提升在局部变量使用时会引起混乱,比如在赋值前就访问了变量

  3. 建议在函数开始时声明所有的变量

  4. 检查相等和不相等的是===!==,而不是==!=

  5. 最新的JavaScript标准ES6中引入了非提升的变量声明:letconst,某些环境下直接禁用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中将值分为truthyfalsy,其中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.namebar["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.314Z
  • toLocalString()返回类似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的内容,返回trueorfalse

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 strictthis会是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的继承是面向原型的继承,支持单一继承和动态创建与修改。

所以原型赋值可以看作是在指定父类。

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 */

BigInt

BigInt - JavaScript | MDN (mozilla.org)

posted @ 2022-06-22 14:58  Frank_Ou  阅读(202)  评论(0编辑  收藏  举报