js基础
js的数据
不区分整数和浮点数 3 / 2 === 1.5
支持进制和科学计数
console.log(0b111110111); // 503
console.log(0o767); // 503
console.log(0x1f7); // 503
console.log(5.03e2); // 503
igInt: 任意长度的整数, 使用后缀'n' 表示
console.log(3n/2n); // 1n
Math对象
Math.sin(3.5); // 3.5rad
const circumference = 2 * Math.PI * r;
字符串转换数字
console.log(parseInt("10"));
console.log(parseFloat("10.5"));
console.log(parseInt("0x10"));
parseInt("hello", 10); // NaN
数字转换成字符串
"" + 12345; // "12345"
比较 == 是类型自适应的, 1 == true, === 不是, 1 !== true
特殊数字
isNaN(NaN); //true
1 / 0; // Infinity
-1 / 0; // -Infinity
字符串有用的方法
console.log("hello".toUpperCase());
console.log("hello".replace("h", "H"));
JavaScript 中的字符串是 Unicode 字符序列。更准确地说,它们是 UTF-16 编码的。
console.log("你好,世界!");
模板字符串,使用反引号``
let age = 10;
console.log(`I am ${age} years old.`); // Template literal
js的数组
const t = [1, -1, 3];
console.log(t.length); // 3
console.log(t[1]);
t.forEach((value) => {
console.log(value);
});
js之中,const的数组意味着这个指针是const的,不意味着数组元素不可变化
因而改变数组的好的编程范式是使用 concat,它会创建一个新的数组
t.push(5); // 改变了原来的t
t.concat(5); // 新建了一个copy再push
console.log(t);
方法名称 | 描述 |
---|---|
a.toString() | 返回一个包含数组中所有元素的字符串,每个元素通过逗号分隔。 |
a.toLocaleString() | 根据宿主环境的区域设置,返回一个包含数组中所有元素的字符串,每个元素通过逗号分隔。 |
a.concat(item1[, item2[, ...[, itemN]]]) | 返回一个数组,这个数组包含原先 a 和 item1、item2、……、itemN 中的所有元素。 |
a.join(sep) | 返回一个包含数组中所有元素的字符串,每个元素通过指定的 sep 分隔。 |
a.pop() | 删除并返回数组中的最后一个元素。 |
a.push(item1, ..., itemN) | 将 item1、item2、……、itemN 追加至数组 a 。 |
a.reverse() | 数组逆序(会更改原数组 a )。 |
a.shift() | 删除并返回数组中第一个元素。 |
a.slice(start, end) | 返回子数组,以 a[start] 开头,以 a[end] 前一个元素结尾。 |
a.sort([cmpfn]) | 依据可选的比较函数 cmpfn 进行排序,如果未指定比较函数,则按字符顺序比较(即使被比较元素是数字)。 |
a.splice(start, delcount[, item1[, ...[, itemN]]]) | 从 start 开始,删除 delcount 个元素,然后插入所有的 item 。 |
a.unshift(item1[, item2[, ...[, itemN]]]) | 将 item 插入数组头部,返回数组新长度(考虑 undefined )。 |
map方法的用处示例
const t2 = t.map((value) => "<li>" + value + "</li>");
console.log(t2);
解构赋值
const [first, second, ...rest] = t;
console.log(first, second); // 1, -1
console.log(rest);
js的对象:键值对的形式
const obj = {
key: 0,
key2: "haha",
key3: false,
key4:()=>{
console.log("Hello!");
}
}
var obj = {
name: "Carrot",
_for: "Max", //'for' 是保留字之一,使用'_for'代替
details: {
color: "orange",
size: 12,
}, // 对象里面也可以是函数和对象
};
obj.details.color; // orange
obj["details"]["size"]; // 12
要访问对象内的属性
console.log(obj.key);
console.log(obj[key]);
obj.newkey="New key";
obj['split word attribute should be in bracket']=0;
js的循环
类似c++的for, while, do-while是支持的
还支持
for(let value of array){}
for(let key in obj){}
js的逻辑运算符
同c++
let name = cachedName || (cacheName = getName());
var allowed = age > 18 ? "yes" : "no";
// 有switch
switch (a) {
case 1: // 继续向下
case 2:
eatIt();
break;
default:
doNothing();
}
js的函数
箭头函数
const f = (p1, p2)=>{
return p1+p2;
}
const f2 = p1 => {
return p1*p1;
}
const f3 = p=>p*p;
const t=[1,2,3];
const tSquared = t.map(p=>p*p)
函数的参数
function add() {
let sum = 0;
for (let i = 0; i < arguments.length; i++) {
sum += arguments[i];
}
return sum;
}
console.log(add(1, 2, 3, 4, 5));
// 或者
function add2(...args) {
let sum = 0;
for (let value of args) {
sum += value;
}
return sum;
}
// 又或者想传递一个数组作为参数列表
let array = [1,2,3];
add.apply(null, array);
this: JavaScript中this的值是根据方法被调用的方式来定义的
const obj = {
name: "object",
value: 42,
call: () => {
console.log("The secret is " + this.value);
},
};
obj.call();
// 得到 undefined, 原因在于this不绑定 ()=>{}匿名函数的定义,而是绑定调用处的全局对象,全局对象没有call()方法,因而是undefined
const obj2 = {
name: "object",
value: 42,
call: function() {
console.log("The secret is " + this.value);
},
};
obj.call();
// 正常得到42
内部函数:可以访问外部函数的变量(默认全部都可见)
js的OOP
一个不太好的版本:问题在于每次调用构造函数都构造了一次函数对象
function Person(first, last) {
this.first = first;
this.last = last;
this.fullName = function () {
return this.first + " " + this.last;
};
this.fullNameReversed = function () {
return this.last + ", " + this.first;
};
}
var s = new Person("Simon", "Willison");
好的实现:使用了原型链prototype
Person.prototype
是一个可以被Person
的所有实例共享的对象。它是一个名叫原型链(prototype chain)的查询链的一部分:当你试图访问Person
某个实例(例如上个例子中的 s)一个没有定义的属性时,解释器会首先检查这个Person.prototype
来判断是否存在这样一个属性。所以,任何分配给Person.prototype
的东西对通过this
对象构造的实例都是可用的。这个特性功能十分强大,JavaScript 允许你在程序中的任何时候修改原型(prototype)中的一些东西,也就是说你可以在运行时 (runtime) 给已存在的对象添加额外的方法:
function Person(first, last) {
this.first = first;
this.last = last;
}
Person.prototype.fullName = function () {
return this.first + " " + this.last;
};
Person.prototype.fullNameReversed = function () {
return this.last + ", " + this.first;
};
Person.prototype.toString = function () {
return "<Person: " + this.fullName() + ">";
};
var s = new Person("Simon", "Willison");
String.prototype.bracket = function () {
return "(" + this + ")";
};
console.log("Hello".bracket()); // (Hello)
function Person(first, last) {
this.first = first;
this.last = last;
}
Person.prototype.fullName = function(){
return this.first + " " + this.last;
}
Person.prototype.fullNameReversed = function(){
return this.last + ", " + this.first;
}
var s = new Person("Simon", "Willison");
js的闭包
闭包
闭包是 JavaScript 中最强大的抽象概念之一——但它也是最容易造成困惑的。它究竟是做什么的呢?
JSCopy to Clipboard
function makeAdder(a) {
return function (b) {
return a + b;
};
}
var add5 = makeAdder(5);
var add20 = makeAdder(20);
add5(6); // ?
add20(7); // ?
makeAdder
这个名字本身,便应该能说明函数是用来做什么的:它会用一个参数来创建一个新的“adder”函数,再用另一个参数来调用被创建的函数时,makeAdder
会将一前一后两个参数相加。从被创建的函数的视角来看的话,这两个参数的来源问题会更显而易见:新函数自带一个参数——在新函数被创建时,便钦定、钦点了前一个参数(如上方代码中的 a、5 和 20,参考
makeAdder
的结构,它应当位于新函数外部);新函数被调用时,又接收了后一个参数(如上方代码中的 b、6 和 7,位于新函数内部)。最终,新函数被调用的时候, 前一个参数便会和由外层函数传入的后一个参数相加。这里发生的事情和前面介绍过的内嵌函数十分相似:一个函数被定义在了另外一个函数的内部,内部函数可以访问外部函数的变量。唯一的不同是,外部函数已经返回了,那么常识告诉我们局部变量“应该”不再存在。但是它们却仍然存在——否则
adder
函数将不能工作。也就是说,这里存在makeAdder
的局部变量的两个不同的“副本”——一个是a
等于 5,另一个是a
等于 20。那些函数的运行结果就如下所示:add5(6); // 返回 11
add20(7); // 返回 27下面来说说,到底发生了什么了不得的事情。每当 JavaScript 执行一个函数时,都会创建一个作用域对象(scope object),用来保存在这个函数中创建的局部变量。它使用一切 被传入函数的变量进行初始化(初始化后,它包含一切被传入函数的变量)。这与那些保存的所有全局变量和函数的全局对象(global object)相类似,但仍有一些很重要的区别:第一,每次函数被执行的时候,就会创建一个新的,特定的作用域对象;第二,与全局对象(如浏览器的
window
对象)不同的是,你不能从 JavaScript 代码中直接访问作用域对象,也没有 可以遍历当前作用域对象中的属性 的方法。所以,当调用
makeAdder
时,解释器创建了一个作用域对象,它带有一个属性:a
,这个属性被当作参数传入makeAdder
函数。然后makeAdder
返回一个新创建的函数(暂记为adder
)。通常,JavaScript 的垃圾回收器会在这时回收makeAdder
创建的作用域对象(暂记为b
),但是,makeAdder
的返回值,新函数adder
,拥有一个指向作用域对象b
的引用。最终,作用域对象b
不会被垃圾回收器回收,直到没有任何引用指向新函数adder
。作用域对象组成了一个名为作用域链(scope chain)的(调用)链。它和 JavaScript 的对象系统使用的原型(prototype)链相类似。
一个闭包,就是 一个函数 与其 被创建时所带有的作用域对象 的组合。闭包允许你保存状态——所以,它们可以用来代替对象。这个 StackOverflow 帖子里有一些关于闭包的详细介绍。
js的正则
创建正则,使用 /exp/ 或者 RegExp
常用方法, test, exec, match, replace
const regex = /ab+c/g;
const regex2 = new RegExp("ab+c", "g");
console.log(regex.test("abc")); // 输出:true
console.log(regex.test("ac")); // 输出:false
console.log(regex.exec("abc")); // 输出:[ 'abc', index: 0, input: 'abc', groups: undefined ]
console.log(regex.exec("ac")); // 输出:null
const str = 'abc abc';
console.log(str.match(regex)); // 输出:[ 'abc', 'abc' ]
console.log(str.replace(regex, 'xyz')); // 输出:'xyz xyz'
// /[exp]/[flags]
// RegExp("[exp]", [flags]);
// what are flags? g 全局匹配, i 忽略大小写, u unicode......
js的异步
async function foo() 创建异步函数,所有的异步函数都会返回一个promise
对一个promise有then方法
foo().then(dosomething())
异步函数还有await关键字,用于在异步函数内部指示暂停直至promise被解析或拒绝
async function foo() {
try {
const response = await fetch('https://api.example.com/data');
// 此处会等待response被解析(成功fetch)或拒绝(fetch失败,转到err), 再运行下一行
const data = await response.json();
// 此处同理
console.log(data);
// 如果没有两个await,这里大概率是undefined
} catch (err) {
console.error('An error occurred:', err);
}
}
foo();
async.parallel方法
接受一个函数数组(其中每个函数都可以有一个回调函数作为参数) 和 一个最终回调函数(参数为一个错误对象和一个结果数组)
并行地执行函数数组内的每个函数,当他们全部完成时,执行最终回调函数
async.parallel([func1(callback1), func2(callback2), ...], finalcallback(err, results){})
也可以将函数数组改为对象,其中每个属性代表一个函数
示例之中exec调用callback的方法已经被mongoose弃用,用 Promise.all达成并行效果
exports.genre_detail = async function (req, res, next) {
try {
const genre = Genre.findById(req.params.id).exec();
const genre_books = Book.find({ genre: req.params.id }).exec();
const results = await Promise.all([genre, genre_books]); //这里一定要await 否则下面是undefined
if (results[0] == null) {
var err = new Error("Genre not found");
err.status = 404;
return next(err);
}
res.render("genre_detail", {
title: "Genre Detail",
genre: results[0],
genre_books: results[1],
});
} catch (err) {
return next(err);
}
};