js学习1
date: 2024/5/17
基础
ES5 规范为了保证旧的功能能够使用,大部分的修改是默认不生效的,"use strict" 来明确地激活这些特性。当你的代码全都写在了 class 和 module 中时,则可以将 "use strict"; 这行代码省略掉。
"use strict";// 代码以现代模式工作,出现在脚本的最顶部,否则严格模式可能无法启用。
let hello='hello world!'//声明变量
const myBirthday = '18.04.1982';//声明常量
类型:Number、BigInt、String、Boolean、null、undefined、Object 和 Symbol (typeof 运算符返回参数的类型)。显式地调用 String(value)进行类型转换
交互:alert、prompt 和 confirm
比较字符串的大小时,是按照字典顺序逐个比较;不同类型的值比较会转换为数字在进行比较,那么就存在一个问题:普通的相等性检查 == 存在一个问题,它不能区分出 0 和 false;也同样无法区分空字符串和 false;严格相等运算符 === 在进行比较时不会做任何的类型转换。相等性检查 == 和普通比较符 > < >= <= 的代码逻辑是相互独立的;进行值的比较时,null 会被转化为数字0;undefined 和 null 在相等性检查 == 中不会进行任何的类型转换。undefined 不应该被与其他值进行比较,undefined在比较中被转换为了NaN。
当一个值既不是 null 也不是 undefined 时,我们将其称为“已定义的(defined)”。a ?? b 的结果是:
- 如果
a是已定义的,则结果为a, - 如果
a不是已定义的,则结果为b。
函数支持默认值:function showMessage(from, text = "no text given")
function sayHi() {//函数声明
alert( "Hello" );
}
let sayHi = function() {// 函数表达式
alert( "Hello" );
};
回调函数:ask 的两个参数值 showOk 和 showCancel 可以被称为 回调函数 或简称 回调。主要思想是我们传递一个函数,并期望在稍后必要时将其“回调”。
function ask(question, yes, no) {
if (confirm(question)) yes()
else no();
}
ask(
"Do you agree?",
function() { alert("You agreed."); },
function() { alert("You canceled the execution."); }
);
箭头函数:只有一个参数,还可以省略掉参数外的圆括号;如果没有参数,括号则是空的(但括号必须保留)
let func = (arg1, arg2, ..., argN) => expression;//它是下面这段代码的更短的版本
let func = function(arg1, arg2, ..., argN) {
return expression;
};
//多行箭头函数
let sum = (a, b) => { // 花括号表示开始一个多行函数
let result = a + b;
return result; // 如果我们使用了花括号,那么我们需要一个显式的 “return”
};
debugger; // <-- 调试器会在这停止
使用以下 JavaScript 库进行测试:
- Mocha —— 核心框架:提供了包括通用型测试函数
describe和it,以及用于运行测试的主函数。 - Chai —— 提供很多断言(assertion)支持的库。它提供了很多不同的断言,现在我们只需要用
assert.equal。 - Sinon —— 用于监视函数、模拟内建函数和其他函数的库,我们在后面才会用到它。
转译器(Transpilers):以解析(“阅读和理解”)现代代码,并使用旧的语法结构对其进行重写,进而使其也可以在旧的引擎中工作。Babel 是最著名的转译器之一。
垫片(Polyfills):新的语言特性可能不仅包括语法结构和运算符,还可能包括内建函数。在一些(非常过时的)JavaScript 引擎中没有 Math.trunc 函数,更新/添加新函数的脚本被称为“polyfill”。它“填补”了空白并添加了缺失的实现。
对象
基础
对象用来存储键值对和更复杂的实体。通过使用带有可选 属性列表 的花括号 {…} 来创建对象。一个属性就是一个键值对(“key: value”),其中键(key)是一个字符串(也叫做属性名),值(value)可以是任何值。
//创建空对象
let user = new Object(); // “构造函数” 的语法
let user = {}; // “字面量” 的语法
let user = { // 一个对象
name: "John", // 键 "name",值 "John"
age: 30 // 键 "age",值 30
};
alert( user.age ); // 30,读取值
user.isAdmin = true;//添加值
delete user.age;//删除值
//多字词语来作为属性名
"likes birds": true // 多词属性名必须加引号
对于多词属性,点操作就不能用了,有另一种方法,就是使用方括号,可用于任何字符串user["likes birds"] = true;
计算属性:如果一个用户输入 "apple",bag 将变为 {apple: 5}。
let fruit = prompt("Which fruit to buy?", "apple");
let bag = {
[fruit]: 5, // 属性名是从 fruit 变量中得到的
};
alert( bag.apple ); // 5 如果 fruit="apple"
属性简写
function makeUser(name, age) {
return {
name: name,
age: age,
// ……其他的属性
};
}
let user = makeUser("John", 30);
alert(user.name); // John
可以用 name 来代替 name:name 像下面那样:
function makeUser(name, age) {
return {
name, // 与 name: name 相同
age, // 与 age: age 相同
// ...
};
}
属性命名没有限制。属性名可以是任何字符串或者 symbol。其他类型会被自动地转换为字符串。一个名为 __proto__ 的属性。我们不能将它设置为一个非对象的值。能够被访问任何属性。即使属性不存在也不会报错!
读取不存在的属性只会得到 undefined。检查属性是否存在的操作符 "in":"key" in object。遍历属性,整数属性会被进行排序,其他属性则按照创建的顺序显示。
for (key in object) {
// 对此对象属性中的每个键执行的代码
}
引用、复制、this、new、转换
对象是“通过引用”存储和复制的,而原始类型:字符串、数字、布尔值等 —— 总是“作为一个整体”复制。
赋值了对象的变量存储的不是对象本身,而是该对象“在内存中的地址” —— 换句话说就是对该对象的“引用”。
复制一个对象:创建一个新对象,通过遍历已有对象的属性,并在原始类型值的层面复制它们;可以使用 Object.assign 方法。Object.assign(dest, [src1, src2, src3...])。深层克隆:我们可以使用递归来实现它。采用现有的实现,例如 lodash 库的 _.cloneDeep(obj)。
作为对象属性的函数被称为 方法。为了访问该对象,方法中可以使用 this 关键字。 this 可以用于任何函数,即使它不是对象的方法。this 的值是在代码运行时计算出来的,它取决于代码上下文。箭头函数有些特别:它们没有自己的 this。如果我们在这样的函数中引用 this,this 值取决于外部“正常的”函数。
user = {
sayHi: function() {
alert("Hello");
}
};
// 方法简写看起来更好,对吧?
let user = {
sayHi() { // 与 "sayHi: function(){...}" 一样
alert("Hello");
}
};
new User(...) 做的就是类似的事情,从技术上讲,任何函数(除了箭头函数,它没有自己的 this)都可以用作构造器。可以使用 new.target 属性来检查它是否被使用 new 进行调用了。对于常规调用,它为 undefined,对于使用 new 的调用,则等于该函数。带有对象的 return 返回该对象,在所有其他情况下返回 this。
function User(name) {
// this = {};(隐式创建)
// 添加属性到 this
this.name = name;
this.isAdmin = false;
// return this;(隐式返回)
}
可选链?. 前面的值为 undefined 或者 null,它会停止运算并返回 undefined。 ?.() 用于调用一个可能不存在的函数。语法 ?.[] 也可以使用。
let user = {}; // user 没有 address 属性
alert( user?.address?.street ); // undefined(不报错)
delete user?.name; // 如果 user 存在,则删除 user.name
“symbol” 值表示唯一的标识符。可以使用 Symbol() 来创建这种类型的值,对象字面量 {...} 中使用 symbol,则需要使用方括号把它括起来。symbol 属性不参与 for..in 循环。全局 symbol,该调用会检查全局注册表,如果有一个描述为 key 的 symbol,则返回该 symbol,否则将创建一个新 symbol(Symbol(key)),并通过给定的 key 将其存储在注册表中。所有 symbol 都具有 description 属性。JavaScript 内部有很多“系统” symbol,我们可以使用它们来微调对象的各个方面。
// id 是描述为 "id" 的 symbol
let id = Symbol("id");
// 从全局注册表中读取
let id = Symbol.for("id"); // 如果该 symbol 不存在,则创建它
// 再次读取(可能是在代码中的另一个位置)
let idAgain = Symbol.for("id");
// 相同的 symbol
alert( id === idAgain ); // true
// 通过 name 获取 symbol
let sym = Symbol.for("name");
// 通过 symbol 获取 name
alert( Symbol.keyFor(sym) ); // name,适用于全局symbol
对象转换:单独对象在全局中都为true,不存在为false。只有字符串和数字转换。数字转换发生在对象相减或应用数学函数时。字符串转换 —— 通常发生在我们像 alert(obj) 这样输出一个对象和类似的上下文中。
hint:string,number,default
为了进行转换,JavaScript 尝试查找并调用三个对象方法:
- 调用
obj[Symbol.toPrimitive](hint)—— 带有 symbol 键Symbol.toPrimitive(系统 symbol)的方法,如果这个方法存在的话, - 否则,如果 hint 是
"string"—— 尝试调用obj.toString()或obj.valueOf(),无论哪个存在。 - 否则,如果 hint 是
"number"或"default"—— 尝试调用obj.valueOf()或obj.toString(),无论哪个存在。