<link> 元素链接外部样式表,使用 <style> 元素向 HTML 嵌入内部样式表,而 JavaScript 这里只需一个元素——<script>。<script>function (){}</script>
<script src="demo.js" defer></script>
使用 defer 告诉浏览器在遇到 <script> 元素时继续下载 HTML 内容,浏览器遇到 async 脚本时不会阻塞页面渲染,而是直接下载然后运行。但是,一旦下载完成,脚本就会执行,从而阻止页面渲染。脚本的运行次序无法控制。当页面的脚本之间彼此独立,且不依赖于本页面的其他任何脚本时,async 是最理想的选择。
使用 defer 属性加载的脚本将按照它们在页面上出现的顺序加载。在页面内容全部加载完毕之前,脚本不会运行,如果脚本依赖于 DOM 的存在(例如,脚本修改了页面上的一个或多个元素),这一点非常有用。
js操作的html元素必须先于js脚本加载,否则会发生错误,因为html元素还不存在,无法操纵
三种脚本加载方法的工作原理:默认情况下,在获取和执行 JavaScript 时,解析过程被阻塞。使用 async 时,解析暂停,仅执行。使用 defer 时,解析不会暂停,但在解析完所有其他内容后才开始执行
脚本调用策略小结:
<script> 元素中。侦听事件发生的结构称为事件监听器(Event Listener),响应事件触发而运行的代码块被称为事件处理器(Event Handler)。
undefined,null,0,NaN,"")new Symbol()。var obj = {
// __proto__
__proto__: theProtoObj,
// Shorthand for ‘handler: handler’
handler,
// Methods
toString() {
// Super calls
return "d " + super.toString();
},
// Computed (dynamic) property names
["prop_" + (() => 42)()]: 42,
};
var 或 let 语句声明的变量,如果没有赋初始值,则其值为 undefined。undefined 的类型是 undefinednull 表示变量不引用任何对象,null 的类型是 objectNaN 是一个表示非数字的值。它是全局作用域中的一个变量。NaN:parseInt("blabla")、Number(undefined),或隐式转换,如 Math.abs(undefined))Math.sqrt(-1))0 * Infinity、1 ** Infinity、Infinity / Infinity、Infinity - Infinity)NaN 的方法或表达式(例如,7 ** NaN、7 * "blabla")——这意味着 NaN 具有传染性Date new Date("blabla").getTime()、"".charCodeAt(1))isNaN(x):如果 x 是 NaN,或者将x强转为数字后结果是 NaN,返回 trueNumberisNaN(x):如果 x 是 NaN,返回 true运算符
typeof 运算符:返回变量类型
==:测试值是否相同,但是数据类型可能不同
===:测试值和数据类型是否相同
模板字面量:反引号声明的字符串,可以在 ${} 中包装 JavaScript 变量或表达式;模板字符串会保留源代码中的换行符,因此可以编写跨越多行的字符串
与或非:&& || !
逻辑空赋值运算符(x ??= y):仅在 x 是空值(null 或 undefined)时对其赋值 x = y
逻辑与赋值(x &&= y):运算仅在 x 为真值时为其赋值。
逻辑或赋值(x ||= y):运算仅在 x 为假值时为其赋值。
delete 操作符:删除一个对象的属性(不是继承而来的属性)或者一个数组中某一个键值delete objectName.property; delete trees[3];
in 操作符:如果所指定的属性确实存在于所指定的对象中,则会返回 true, propNameOrNumber in objectName;
instanceof 操作符:如果所判别的对象确实是所指定的类型,则返回 true, obj instanceof TypeName
扩展运算符(...):用于将数组或可迭代对象中的元素展开到一个新的数组或函数调用中。
new.target:检测函数或构造方法是否是通过 new 运算符被调用的,在通过 new 运算符被初始化的函数或构造方法中,new.target 返回一个指向构造方法或函数的引用。在普通的函数调用中,new.target 的值是 undefined。
function Foo() {
if (!new.target) throw "Foo() must be called with new";
console.log("Foo instantiated with new");
}
Foo(); // throws "Foo() must be called with new"
new Foo(); // logs "Foo instantiated with new"
解构:从数组,对象或函数中获取值,语法糖
字符串 str='mozilla'
str.length // 7str[1] // ostr.indexOf('zilla') // 2,返回 zmy 的起始位置,找不到返回 -1str.slice(2,5) 左闭右开,// zil`str.replace("moz", "van") // vanillastr.split(',') 使用逗号分隔,返回一个数组parseInt('123', radix):解析为 radix 进制的整数,前后的空格被忽略parseFloat('1.23'):解析为浮点数+'123':将字符串转为数字数组
array.join('-') 数组元素使用 - 拼接为一个字符串array.toString() 使用逗号拼接数组array.push('zmy') 末尾添加元素,返回新数组长度array.pop() 删除最后一个元素,返回被删除的元素array.shift('zmy') 开头添加元素array.unshift() 删除开头元素slice() 方法从数组提取一个片段,并作为一个新数组返回forEach() 方法提供了遍历数组元素的其他方法:在数组定义时省略的元素不会在 forEach 遍历时被列出,但是手动赋值为 undefined 的元素是会被列出的:循环
for...in 语句循环一个指定的变量来循环一个对象所有可枚举的属性(循环的是属性)for...of 语句在可迭代对象(包括 Array、Map、Set、arguments 等等)上创建了一个循环,循环的是每一个元素// Map
sayings.set("dog", "woof");
sayings.set("cat", "meow");
sayings.set("elephant", "toot");
sayings.size; // 3
sayings.get("dog"); // woof
sayings.get("fox"); // undefined
sayings.has("bird"); // false
sayings.delete("dog");
sayings.has("dog"); // false
// 循环Map
for (const [key, value] of sayings) {
console.log(`${key} goes ${value}`);
}
// Set
const mySet = new Set();
mySet.add(1);
mySet.add("some text");
mySet.add("foo");
mySet.has(1); // true
mySet.delete("foo");
mySet.size; // 2
// 循环Set
for (const item of set) {
console.log(item);
}
函数
return 语句,则它默认返回 undefinedfunction add(a, b) { return a + b },这种创建函数的形式,也被称为函数声明。它总是被提升的,这样你就能在函数定义之前调用该函数。(function add(a, b) { return a + b })(a, b) => { return a + b } 函数体只有一行,return 可省略:() => x 是 () => { return x; } 的简写,代表有返回值。这点在应用Promise时很重要。arguments 是一个对应于传递给函数的参数的类数组对象。参数本质上是按值传递给函数的——因此,即使函数体的代码为传递给函数的参数赋了新值,这个改变也不会反映到全局或调用该函数的代码中。
如果传的是对象,改变对象的属性对外部是可见的。
Function 对象,由 Function 构造函数创建的函数不会创建当前环境的闭包,它们总是被创建于全局环境,因此在运行时它们只能访问全局变量和自己的局部变量,不能访问它们被 Function 构造函数创建时所在的作用域的变量。eval() 函数会将传入的字符串当做 JavaScript 代码进行执行。嵌套函数和闭包:
变量提升(hoisted):变量的声明在使用之后,使用时无感,像是声明被提升到使用之前一样
// 函数声明
foo();
function foo(){}
// 函数表达式
baz();
var baz = function(){}
addEventListener('click',function(){}),可以为同一个事件添加多个处理器btn.onclick = ()=>{} btn.onclick = clickFuntion,添加多个处理器会被覆盖<button onclick="bgChange()">点击</button>(event, evt, e):它被自动传递给事件处理函数 function bgChange(event),以提供额外的功能和信息。事件对象 e 的 target 属性始终是事件刚刚发生的元素的引用。一些事件对象添加了与该特定类型的事件相关的额外属性。// 对象字面量
const person = {
name: ["Bob", "Smith"],
age: 32,
bio: function () { // 可以简写为 bio(){ }
console.log(`${this.name[0]} ${this.name[1]} 现在 ${this.age} 岁了。`);
},
introduceSelf: function () {
console.log(`你好!我是 ${this.name[0]}。`);
},
};
数据,被称为对象的属性。函数,称为对象的方法。
索引:person.name 或 person["name"],如果属性为变量必须使用方括号 let propertyName="age"; person[propertyName],点表示法只能接受字面量的成员属性的名字,不接受表示名称的变量。
方括号中的所有键都将转换为字符串类型,因为 JavaScript 中的对象只能使用 String 类型作为键类型。
用构造函数构造对象
function createPerson(name) {
const obj = {};
obj.name = name;
obj.introduceSelf = function () {
console.log(`你好!我是 ${this.name}。`);
};
return obj;
}
const salva = createPerson('Salva');
class。调用构造函数需要加 newfunction Person(name) {
this.name = name;
this.introduceSelf = function () {
console.log(`你好!我是 ${this.name}。`);
};
}
const salva = new Person("Salva");
name 声明是可选的,因为在构造函数中的 this.name = name 这行代码会在初始化 name 属性前自动创建它extends 关键字来声明这个类继承自另一个类。子类构造函数中需要做的第一件事是使用 super() 调用父类的构造函数,并传递 name 参数。在类中不能使用 name:'zmy' 这样的形式定义属性,这种形式只适用于对象字面量,应使用 name='zmy' this.name='zmy'
class Person {
name;
constructor(name) {
this.name = name;
}
introduceSelf() {
console.log(`Hi! I'm ${this.name}`);
}
}
const giles = new Person("Giles");
封装:私有数据属性必须在类的声明中声明,而且其名称需以 # 开头。声明私有方法。而且名称也是以 # 开头
枚举一个对象的所有属性
for...in 循环 该方法依次访问一个对象及其原型链中所有可枚举的属性。Object.keys(obj) 该方法返回对象 obj 自身包含(不包括原型中)的所有可枚举属性的名称的数组。Object.getOwnPropertyNames(obj) 该方法返回对象 obj 自身包含(不包括原型中)的所有属性 (无论是否可枚举) 的名称的数组。getter 与 setter:getter 是一个获取某个特定属性的值的方法。一个 setter 是一个设定某个属性的值的方法
var o = {
a: 7,
get b() {
return this.a + 1;
},
set c(x) {
this.a = x / 2;
},
};
console.log(o.a); // 7
console.log(o.b); // 8
o.c = 50;
console.log(o.a); // 25
使用 getter 和 setter 时相当于定义了新的属性
定义一个已经声明的函数作为的 getter 和 setter 方法,使用Object.defineProperty或Object.prototype.defineGetter()和Object.prototype.defineSetter()
属性描述符:用于描述一个对象属性的配置,使用 Object.defineProperty(obj, prop, descriptor) 精确控制属性的特性,使用 Object.getOwnPropertyDescriptor(obj, prop) 查看属性配置,它返回一个对象,该对象描述给定对象 obj 上特定属性 prop(即直接存在于对象上而不在对象的原型链中的属性)的配置
对象的属性分为两种类型:数据属性和访问器属性,
对象中存在的属性描述符有两种主要类型:数据描述符和访问器描述符。数据描述符和访问器描述符都是对象(键值对)。数据描述符是一个具有可写或不可写值的属性。访问器描述符是由 getter/setter 函数对 描述的属性。描述符只能是这两种类型之一,不能同时为两者。
数据描述符(数据属性的描述符):数据描述符将键与值相关联。它可以通过以下键来描述:
value:与属性相关联的值。可以是任何有效的 JavaScript 值(数字、对象、函数等)。默认值为 undefined。writable:如果可以使用赋值运算符更改,则为 true。默认值为 false。enumerable:表示是否可以通过 for...in 循环来枚举属性。默认值为 falseconfigurable:表示该属性是否可以删除,是否可以更改为访问器属性,并可以更改其特性。默认为 false,当为 false 时:value 可以被更改,writable 可以更改为 false)。访问器描述符(访问器属性的描述符):将键与两个访问器函数(get 和 set)相关联,以获取或者存储值。
get:用作属性 getter 的函数。当访问该属性时,将不带参地调用此函数,并将 this 设置为通过该属性访问的对象。默认值为 undefined。set:用作属性 setter 的函数,当该属性被赋值时,将调用此函数,并带有一个参数(要赋给该属性的值),并将 this 设置为通过该属性分配的对象。默认值为 undefined。enumerable:表示是否可以通过 for...in 循环来枚举属性。默认值为 falseconfigurable:表示该属性是否可以删除,是否可以更改为访问器属性,并可以更改其特性。默认为 false,当为 false 时:value 可以被更改,writable 可以更改为 false)。如果描述符同时具有 [value 或 writable] 和 [get 或 set] 键,则会抛出异常。
旧描述符的 configurable 特性被设置为 false, 则该属性被称为不可配置的。
get和set 描述符,则该属性则成为访问器类型的属性)。writable: true 的数据属性例外,可以修改其值并将 writable 特性从 true 改为 false。value 和 writable)时,会抛出 TypeError,除非在数据属性上定义一个与原始值相同的值。当前属性是可配置的时
undefined 可以有效地删除它undefined)for...in、Object.keys() 等)出现,它们的值可以被更改,也可以被删除。Object.defineProperty() 添加的属性是不可写、不可枚举和不可配置的writable 属性指示是否可以通过 = 赋值,对于不可写但可配置的属性,虽然不能通过 = 直接修改,但可以使用 Object.defineProperty() 修改value的配置来达到修改的目的const o = {};
o.a = 1;
// 等价于:
Object.defineProperty(o, "a", {
value: 1,
writable: true,
configurable: true,
enumerable: true,
});
function Archiver() {
let temperature = null;
const archive = [];
Object.defineProperty(this, "temperature", {
get() {
console.log("get!");
return temperature;
},
set(value) {
temperature = value;
archive.push({ val: temperature });
},
});
this.getArchive = () => archive;
}
JavaScript 中所有的对象都有一个内置属性,称为它的 prototype(原型)。它本身是一个对象,故原型对象也会有它自己的原型,逐渐构成了原型链。原型链终止于拥有 null 作为其原型的对象上。
js 是通过定义一个指向父类指针的方式来实现继承的。也就是原型链
指向对象原型的属性并不是 prototype。它的名字不是标准的,但实际上所有浏览器都使用 __proto__。 Object.getPrototypeOf(obj) 和 Object.setPrototypeOf(person, personPrototype) 方法来访问和修改原型链。
当试图访问一个对象的属性时:如果在对象本身中找不到该属性,就会在原型中搜索该属性。如果仍然找不到该属性,那么就搜索原型的原型,以此类推,直到找到该属性,或者到达链的末端,在这种情况下,返回 undefined。
对于普通的对象来说,__proto__ 通常指向 Object.prototype。person.__proto__ 等于 Person.prototyp
对于函数来说,__proto__ 通常指向 Function.prototype,Person.__proto__ 等于 Function.prototype。
如果在一个对象中定义了一个属性,而在该对象的原型中存在一个同名的属性,原型中的属性会被遮蔽。
所有的函数 Person() 都有一个名为 prototype 的属性。当调用一个函数作为构造函数时,这个属性被设置为新构造对象 person 的原型,即 person.__proto__ 等于 Person.prototyp。
访问原型:someObject.[[Prototype]], Object.getPrototypeOf(obj), obj.__proto__,可以理解为 [[Prototype]] 就是 __proto__
console.log(person.__proto__ === Person.prototype) // true
console.log(Person.prototype.__proto__ === Object.prototype) // true
console.log(Object.prototype.__proto__ === null) // true
__proto__ 属性是原型链的基础,所有对象(包括函数)都通过 __proto__ 形成一条通往 Object.prototype 的链条,从而实现了继承prototype 是函数特有的属性,而普通对象只有 __proto__ 属性 prototype 属性用于定义所有通过该构造函数创建的实例共享的属性和方法。constructor 是 prototype 对象自有的属性,指向构造函数 Personperson 的 __proto__ 属性指向它的构造函数 Person 的 prototype 属性,Person 的 prototype 属性的 __proto__ 属性又指向 Object 的 prototype 属性,Object 的 prototype 属性指向 null,即 person -> Person.prototype -> Object.prototype -> nullPerson、Object 甚至是 Function) 也是对象,其 __proto__ 属性指向 Function 的 prototype 属性,即 Person.__proto__ = Function.prototype,Object.__proto__ = Function.prototype,Function.__proto__ = Function.prototype,而 Function.prototype 的 __proto__ 属性又指向 Object 的 prototype 属性.prototype 为它的实例提供原型链(橙色链)。二是作为普通对象(是 Function 函数的实例),它自己也有 __proto__,指向 Function.prototype,形成函数对象的原型链

Object.create() 方法创建一个新的对象,并允许你指定一个将被用作新对象原型的对象。即person = Object.create(personPrototype); person.__proto__ 等于 personPrototype
直接在对象中定义的属性,如这里的 name,被称为自有属性,你可以使用静态方法 Object.hasOwn(obj,'propName') 检查一个属性是否是自有属性。也可以在这里使用非静态方法 obj.hasOwnProperty('propName')
记住:原型是一个对象,是对象就有 __proto__ 属性
通过 person = Object.create(personPrototype) 创建的对象,对象的原型就是 personPrototype
通过构造函数创建的 person = new Person() 创建的对象,对象的原型就是 Person.prototyp
// 一个构造函数
function Box(value) {
this.value = value;
}
// 使用 Box() 构造函数创建的所有盒子都将具有的属性
Box.prototype.getValue = function () {
return this.value;
};
const boxes = [new Box(1), new Box(2), new Box(3)];
new Box(1) 是通过 Box 构造函数创建的一个实例。通过构造函数创建的每一个实例都会自动将构造函数的 prototype 属性作为其 [[Prototype]]。即,Object.getPrototypeOf(new Box()) === Box.prototype。Box.prototype 默认具有一个自有属性:constructor,它引用了构造函数本身。即,Box.prototype.constructor === Box。这允许我们在任何实例中访问原始构造函数。
Box.prototype 仅在构造实例时有用。它与 Box.[[Prototype]] 无关,后者是构造函数的自有原型,即 Function.prototype。也就是说,Object.getPrototypeOf(Constructor) === Function.prototype。默认情况下,Constructor.prototype 是一个普通对象——即 Object.getPrototypeOf(Constructor.prototype) === Object.prototype
JavaScript 中的一些字面量语法会创建隐式设置 [[Prototype]] 的实例
// 对象字面量(没有 `__proto__` 键)自动将 `Object.prototype` 作为它们的 `[[Prototype]]`
const object = { a: 1 };
Object.getPrototypeOf(object) === Object.prototype; // true
// 数组字面量自动将 `Array.prototype` 作为它们的 `[[Prototype]]`
const array = [1, 2, 3];
Object.getPrototypeOf(array) === Array.prototype; // true
// 正则表达式字面量自动将 `RegExp.prototype` 作为它们的 `[[Prototype]]`
const regexp = /abc/;
Object.getPrototypeOf(regexp) === RegExp.prototype; // true
[[Prototype]] 只是对象的“内部属性”。
使用不同的方法来创建对象和改变原型链的优缺点
__proto__ 属性指向非对象的值只会被忽略,而非抛出异常。与 Object.prototype.__proto__ setter 相反,对象字面量初始化器中的 __proto__ 是标准化,被优化的。甚至可以比 Object.create 更高效。在创建对象时声明额外的自有属性比 Object.create 更符合习惯。Object.prototype.__proto__ 访问器差异的人可能会将两者混淆。Object.create[[Prototype]],这允许运行时进一步优化对象。还允许使用 Object.create(null) 创建没有原型的对象。Object.setPrototypeOfObject.create(null) 创建的无原型对象设置原型。__proto____proto__ 设置为非对象的值只会被忽略,而非抛出异常。__proto__ 访问器是规范中可选的特性,因此可能无法在所有平台上使用。你几乎总是应该使用 Object.setPrototypeOf 代替要检查对象是否具有在其自身上定义的属性,而不是在其原型链上的某个地方,则有必要使用 obj.hasOwnProperty('propName') 或 Object.hasOwn(obj,'propName') 方法。
const original = {
name: 'Alice',
age: 25,
hobbies: ['reading', 'coding']
};
const copy = original; // 直接赋值
copy.hobbies.push('painting');
copy.name='zmy';
console.log(original); // {name:'Alice, age:25, hobbies:Array(3)}
console.log(copy); // {name:'Alice, age:25, hobbies:Array(3)}
console.log(original.hobbies); // 输出 ["reading", "coding", "painting"]
console.log(copy.hobbies); // 输出 ["reading", "coding", "painting"]
const shallowCopy = Object.assign({}, original); // 浅拷贝
shallowCopy.hobbies.push('painting');
shallowCopy.name='zmy';
console.log(original); // {name:'Alice, age:25, hobbies:Array(3)}
console.log(shallowCopy); // {name:'zmy, age:25, hobbies:Array(3)}
console.log(original.hobbies); // 输出 ["reading", "coding", "painting"]
console.log(shallowCopy.hobbies); // 输出 ["reading", "coding", "painting"]
// 使用 JSON.parse 和 JSON.stringify 进行深拷贝
const deepCopy = JSON.parse(JSON.stringify(original));
deepCopy.hobbies.push('painting');
deepCopy.name='zmy';
console.log(original); // {name:'Alice, age:25, hobbies:Array(2)}
console.log(deepCopy); // {name:'zmy, age:25, hobbies:Array(3)}
console.log(original.hobbies); // 输出 ["reading", "coding"]
console.log(deepCopy.hobbies); // 输出 ["reading", "coding", "painting"]
出现this问题的大多涉及到嵌套函数,遇到这种情况需注意
箭头函数没有自己的this,它从定义的外部作用域获取this
箭头函数的特点
this:箭头函数不会创建自己的 this 上下文。这意味着箭头函数内部的 this 值是从外围作用域继承的。this:箭头函数内部的 this 值是基于词法作用域(即定义它的上下文)来决定的,而不是基于函数调用的位置。arguments 对象:箭头函数没有自己的 arguments 对象,但是可以使用剩余参数语法 (...args) 来模拟 arguments。constructor 属性:箭头函数没有 constructor 属性,因此不能用作构造函数来创建新对象。prototype 属性:箭头函数没有 prototype 属性,因此不能用于原型链继承。使用 Function 对象的实例函数改变this的指向。
Promise就是一个承诺,预期会完成某事(异步操作),然后把对完成后的结果的处理方式(成功和失败完成的回调函数)告诉 Promise,预期的事完成后,他会自动去处理。
then() 本身也会返回一个 Promise,这个 Promise 将指示 then() 中调用的异步函数的完成状态。// 直接传回调函数
createAudioFileAsync(audioSettings, successCallback, failureCallback);
// 绑定回调函数
createAudioFileAsync(audioSettings).then(successCallback, failureCallback);
catch(failureCallback) 等同于 then(null, failureCallback),如果错误处理代码对所有步骤都是一样的,你可以把它附加到链的末尾:then 中的回调函数会接收他的兑现值(就是这个 Promise 中异步函数的返回值),如果上一个 then 中启动一个 Promise 但没有返回,那么就这个 Promise 的的兑现值就无法应用于后续的 then 中的回调函数。doSomething()
.then((url) => {
// fetch(url) 前缺少 `return` 关键字。
fetch(url);
})
.then((result) => {
// result 是 undefined,因为上一个处理器没有返回任何东西。
// 无法得知 fetch() 的返回值,也无法知道它是否成功。
});
经验法则是,每当你的操作遇到一个 Promise,就返回它,并把它的处理推迟到下一个 then 处理器中。
const listOfIngredients = [];
doSomething()
.then((url) => {
// fetch 调用前面现在包含了 `return` 关键字。
return fetch(url)
.then((res) => res.json())
.then((data) => {
listOfIngredients.push(data);
});
})
.then(() => {
console.log(listOfIngredients);
// listOfIngredients 现在将包含来自 fetch 调用的数据。
});
// 将嵌套链扁平化为单链,这样更简单,也更容易处理错误
doSomething()
.then((url) => fetch(url))
.then((res) => res.json())
.then((data) => {
listOfIngredients.push(data);
})
.then(() => {
console.log(listOfIngredients);
});
executor,executor 的参数是 resolve 和 reject 回调函数const myPromise = new Promise((resolve, reject) => {
setTimeout(() => {
if(Math.random() < 0.5)
resolve(value);
else
reject(reason);
}, 300);
});
myPromise.then(onFulfilled, onRejected);
.then() 方法最多接受两个参数;第一个参数是 Promise 兑现时的回调函数 resolve,第二个参数是 Promise 拒绝时的回调函数 reject。resolve,则该 Promise 的状态立即变为已兑现,会立马调用 then 中的兑现回调函数 onFulfilled,传给 resolve 的参数(即Promise的兑现值 value)也是传给 onFulfilled 的参数(也可以理解为用 onFulfilled 替换 resolve),onFulfilled 的返回值又称为 then 返回的 Promise 对象的兑现值。如果 onFulfilled 不是一个函数,则内部会被替换为一个恒等函数((x) => x),它只是简单地将兑现值向后传递。reject,Promise 就变为已拒绝,立即调用 onRejected,传给 rejct 的参数(拒绝值,通常是一个 Error 对象)也就是传给 onRejected 的参数,它的返回值成为 catch 返回的 Promise 的兑现值。如果 onRejected 不是一个函数,则内部会被替换为一个抛出器函数((x) => { throw x; }),它会抛出它收到的拒绝原因。value 或 reason 中有任意一个被省略,Promise 将会被兑现(fulfilled)或拒绝(rejected)为 undefined。then 返回一个 Promise 对象(假设为p),p 的行为取决于处理函数(onFulfilled 和 onRejected)的执行结果,如果处理函数返回:
undefined 作为其兑现值。executor 通常会封装某些提供基于回调的 API 的异步操作 func(x,callback),回调函数 callback 在 executor 代码中定义(resolve, reject) => {},因此它可以访问 resolve 和 reject。
const funcPromise = (x) =>
new Promise((resolve, reject) => {
console.log('resolve1',resolve)
console.log('reject1',reject)
func(x, (error, result) => {
console.log('error',error)
console.log('result',result)
if (error) {
console.log('reject2',reject)
reject(new Error(result));
} else {
console.log('resolve2',resolve)
resolve(result*2);
}
});
});
executor 的完成状态对 Promise 的状态影响有限:
executor 函数的返回值会被忽略。executor 函数中的 return 语句仅影响控制流程,调整函数某个部分是否执行,但不会影响 Promise 的兑现值。如果 executor 函数退出,且未来不可能调用 resolve 或 reject(例如,没有安排异步任务),那么 Promise 将永远保持待定状态。(只有调用了 resolve 或 reject 才会改变Promise的状态)executor 函数中抛出错误,则 Promise 将被拒绝,除非 resolve 或 reject 已经被调用。如果先调用 resolve,则传入的值将解决。
thenable 对象,Promise 可能会保持待定状态;thenable 值的大多数情况下,Promise 变为已兑现状态,如果先调用 reject,则 Promise 立即变为已拒绝状态。
一旦 resolve 或 reject 中的一个被调用,Promise 将保持解决状态。只有第一次调用 resolve 或 reject 会影响 Promise 的最终状态,随后对任一函数的调用都不能更改兑现值或拒绝原因,也不能将其最终状态从“已兑现”转换为“已拒绝”或相反。
如果 executor 抛出错误,则 Promise 被拒绝。但是,如果 resolve 或 reject 中的一个已经被调用(因此 Promise 已经被解决),则忽略该错误。
解决 Promise 不一定会导致 Promise 变为已兑现或已拒绝(即已敲定)。Promise 可能仍处于待定状态,因为它可能是用另一个 thenable 对象解决的,但它的最终状态将与已解决的 thenable 对象一致。
一旦 Promise 敲定,它会(异步地)调用任何通过 then()、catch() 或 finally() 关联的进一步处理程序。最终的兑现值或拒绝原因在调用时作为输入参数传给兑现和拒绝处理程序
例子
const funcPromise = (x) =>
new Promise((resolve, reject) => {
console.log('resolve1',resolve)
console.log('reject1',reject)
func(x, (error, result) => {
console.log('error',error)
console.log('result',result)
if (error) {
console.log('reject2',reject)
reject(new Error(result));
console.log('rejected')
throw 'reject error'
} else {
console.log('resolve2',resolve)
resolve(result*2);
console.log('resolved')
throw 'resolve error'
}
});
});
function func(x,f){
if(x<5){
// throw new Error('a error')
// throw 'a error'
f(true, 3)
}
else{
console.log('success')
f(false,x)
}
}
funcPromise(4)
.then((result) => console.log('the result is ',result))
.catch((error) => console.error("读取文件失败"+error.message));
resolve 函数有以下行为:
TypeError 错误。thenable 的值(基本类型,或一个没有 then 属性或 then 属性不可调用的对象),则该 Promise 对象会被立即以该值兑现。thenable 对象(包括另一个 Promise 实例),则该 thenable 对象的 then 方法将被保存并在未来被调用(它总是异步调用)。then 方法将被调用并传入两个回调函数,这两个函数的行为与传递给 executor 函数的 resolve 和 reject 函数完全相同。如果调用 then 方法时出现错误,则当前的 Promise 对象会被拒绝并抛出这个错误。调用 resolve 函数会使 Promise 对象变为已解决状态,因此再次调用 resolve 或 reject 函数没有任何效果。然而,Promise 对象可能处于以下任何状态之一:待定、已兑现或已拒绝。
错误处理:一遇到异常抛出,浏览器就会顺着 Promise 链寻找下一个 onRejected 失败回调函数或者由 .catch() 指定的回调函数,就像同步代码中的 try 中出现异常会立即跳转到 catch 部分
有可能会在一个回调失败之后继续使用链式操作,即,在 catch 之后再使用一个 then,这对于在链式操作中抛出一个失败之后,再次进行新的操作会很有用
嵌套:嵌套是一种可以限制 catch 语句的作用域的控制结构写法。明确来说,嵌套的 catch 只会捕获其作用域及以下的错误,而不会捕获链中更高层的错误。如果使用正确,可以实现细粒度的错误恢复。
Promise 拒绝事件
当一个 Promise 拒绝事件未被任何处理器处理时,它会冒泡到调用栈的顶部。在 Web 上,当 Promise 被拒绝时,会有下文所述的两个事件之一被派发到全局作用域
rejectionhandled(拒绝已处理):当 Promise 被拒绝、并且在 reject 函数处理该拒绝事件之后会派发此事件。unhandledrejection(拒绝未处理):当 Promise 被拒绝,但没有提供 reject 函数来处理该拒绝事件时,会派发此事件。上述两种事件(类型为 PromiseRejectionEvent)都有两个属性,一个是 promise 属性,该属性指向被拒绝的 Promise,另一个是 reason 属性,该属性用来说明 Promise 被拒绝的原因。
const fetchPromise = fetch(
"https://mdn.github.io/learning-area/javascript/apis/fetching-data/can-store/products.json",
);
console.log(fetchPromise);
fetchPromise.then((response) => {
console.log(`已收到响应:${response.status}`);
});
console.log("已发送请求……");
const fetchPromise = fetch(
"https://mdn.github.io/learning-area/javascript/apis/fetching-data/can-store/products.json",
);
fetchPromise
.then((response) => response.json())
.then((data) => {
console.log(data[0].name);
});
const fetchPromise = fetch(
"bad-scheme://mdn.github.io/learning-area/javascript/apis/fetching-data/can-store/products.json",
);
fetchPromise
.then((response) => {
if (!response.ok) {
throw new Error(`HTTP 请求错误:${response.status}`);
}
return response.json();
})
.then((json) => {
console.log(json[0].name);
})
.catch((error) => {
console.error(`无法获取产品列表:${error}`);
});
Promise 的几种状态:
Promise.all() 方法。它接收一个 Promise 数组,并返回一个单一的 Promise。Promise.all() 返回的 Promise:then() 处理函数并提供一个包含所有响应的数组,数组中响应的顺序与被传入 Promise.all() 的 Promise 的顺序相同。catch() 处理函数被调用,并提供被拒绝的 Promise 所抛出的错误。const fetchPromise1 = fetch(
"https://mdn.github.io/learning-area/javascript/apis/fetching-data/can-store/products.json",
);
const fetchPromise2 = fetch(
"https://mdn.github.io/learning-area/javascript/apis/fetching-data/can-store/not-found",
);
const fetchPromise3 = fetch(
"https://mdn.github.io/learning-area/javascript/oojs/json/superheroes.json",
);
Promise.all([fetchPromise1, fetchPromise2, fetchPromise3])
.then((responses) => {
for (const response of responses) {
console.log(`${response.url}:${response.status}`);
}
})
.catch((error) => {
console.error(`获取失败:${error}`);
});
Promise.any()。在 Promise 数组中的任何一个被兑现时它就会被兑现,如果所有的 Promise 都被拒绝,它也会被拒绝。在这种情况下,我们无法预测哪个获取请求会先被兑现。
Promise.any() 和 Promise.all() 这些方法都是并发运行 Promise 的——一系列 Promise 同时启动,而不是彼此等待。顺序执行也是可能的,这需要一些巧妙的 JavaScript 手段:[func1, func2, func3]
.reduce((p, f) => p.then(f), Promise.resolve())
.then((result3) => {
/* 使用 result3 */
});
// 上面的代码等同于:
Promise.resolve()
.then(func1)
.then(func2)
.then(func3)
.then((result3) => {
/* 使用 result3 */
});
reduce() 方法对数组中的每个元素按序执行一个提供的 reducer 函数,每一次运行 reducer 会将先前元素的计算结果作为参数传入,最后将其结果汇总为单个返回值。const array1 = [1, 2, 3, 4];
// 0 + 1 + 2 + 3 + 4
const initialValue = 0;
const sumWithInitial = array1.reduce(
(accumulator, currentValue) => accumulator + currentValue,
initialValue,
);
console.log(sumWithInitial);
// Expected output: 10
async 关键字提供了一种更简单的方法来处理基于异步 Promise 的代码。在一个函数的开头添加 async,就可以使其成为一个异步函数。
await:在调用一个返回 Promise 的函数之前使用 await 关键字。这使得代码在该点上等待,直到 Promise 被完成,这时 Promise 的响应被当作返回值(原本返回的是一个 Promise 对象),或者被拒绝的响应被作为错误抛出。await 强制异步操作以串联的方式完成。
只能在 async 函数或者是 JavaScript 模块(export,import)中使用 await,这意味着不能在普通脚本使用 await
async 会把返回结果包装成 Promise,await 一个返回 Promise 的函数,会在 Promise 结束时,自动从 Promise 响应中取出结果(resolve 的值)
async 函数返回 Promise 时会自动"展平",不会双重包装。await 一个 Promise<T> 永远得到 T,不是 Promise<T>。
纯 Promise 写法(.then 链式调用)
// 定义:不用 async/await
get<T>(url: string): Promise<T> {
return axiosInstance.get<BaseResponse<T>>(url)
.then(res => res.data.result)
}
// 调用方式 A:用 .then()
http.get<User>('/user/1')
.then(user => {
console.log(user.name) // user: User
})
// 调用方式 B:用 await(在 async 函数内)
async function fetchUser() {
const user = await http.get<User>('/user/1')
console.log(user.name)
}
// 定义:用 async/await
async get<T>(url: string): Promise<T> {
const res = await axiosInstance.get<BaseResponse<T>>(url) // ✅ 内部 await
return res.data.result
}
// 调用方式 A:用 await(在 async 函数内)
async function fetchUser() {
const user = await http.get<User>('/user/1') // ✅ 可以!
console.log(user.name)
}
// 调用方式 B:用 .then()
http.get<User>('/user/1')
.then(user => {
console.log(user.name) // ✅ 也可以!
})
浏览器 API 内置于 Web 浏览器中,能从浏览器和电脑周边环境中提取数据
第三方 API 缺省情况下不会内置于浏览器中,通常必须在 Web 中的某个地方获取代码和信息。
JavaScript、API 和其他 JavaScript 工具之间的关系
常见浏览器 API
DOM(文档对象模型)APIAxios API(三方库,可用于Node.js),Fetch API(内置于浏览器),XMLHttpRequest API, Ajax API<canvas> 元素中的像素数据以创建 2D 和 3D 场景的 Canvas 和 WebGL。常见第三方 API
文档对象模型 DOM
document.querySelector():获取元素的引用,返回 Node 对象,使用ele根据元素名查找,使用#id根据元素id属性查找,使用.class根据class属性查找,document.querySelector("a"):匹配文档中出现的第一个 <a> 元素document.querySelectorAll():匹配文档中与选择器相匹配的每个元素,返回NodeList 的类数组对象(不是一个数组)Document.getElementById():选择一个 id 属性值已知的元素,例如 <p id="myId">My paragraph</p>。ID 作为参数传递给函数,即 const elementRef = document.getElementById('myId')。Document.getElementsByTagName():返回页面中包含的所有已知类型元素的数组。如 <p>、<a> 等。元素类型作为参数传递给函数,即 const elementRefArray = document.getElementsByTagName('p')。Document.createElement('TagName'):创建新的元素Document.createTextNode('text'):创建一个文本节点HTMLElement.style 属性,元素的内联样式Element.setAttribute('prop', 'propValue'):它接受两个参数:想在元素上设置的属性、要为它设置的值export let varNameexport { varName, funName, className }import { varName, funName, className } from 'path/moduleName.js',模块文件的路径是相对于站点根目录的相对路径,<script type="module" src="main.js"></script>export default funName;,或者 export default function(ctx) { },导入默认函数:import funName from "./modules/square.js";默认导出不加大括号,因为每个模块只能有一个默认导出,默认导入时为匿名函数命名
// inside module.js
export { function1 as newFunctionName, function2 as anotherNewFunctionName };
// inside main.js
import { newFunctionName, anotherNewFunctionName } from "/modules/module.js";
// 获取 module.js 中所有可用的导出
import * as Module from "/modules/module.js";
Module.function1();
Module.function2();
$().fun() 和 $.fun() 的区别$().fun() 是在 jQuery 对象上调用的方法,作为一个选择器,被称为 $.fn 命名空间或者 jQuery 原型。其会自动接收和返回选择的对象作为 this (像是成员函数,非静态的)$.fun() 是在 jQuery 命名空间上的,或者称为核心 jQuery 方法。不会传递任何参数,返回值也是不定的。(像是静态方法)$.get( "myhtmlpage.html", myCallBack );$.get( "myhtmlpage.html", function() { myCallBack( param1, param2 );});$(document).ready() 在 DOM 准备后执行,等价于 $()$(window).on("load", function() { ... }) 在整个页面加载好后执行一次var $j = jQuery.noConflict(); 防止命名冲突,命名别名,jQuery.noConflict() 返回 jQuery 函数的别名jQuery.noConflict();
// 局部使用,将 $ 传递给函数
jQuery(document).ready(function( $ ) {
// You can use the locally-scoped $ in here as an alias to jQuery.
$( "div" ).hide();
});
// 使用立即调用函数表达式
(function( $ ) {
// Your jQuery code here, using the $
})( jQuery );
$("a").attr() 作为一个 getter 或 setter 使用:// 作为 setter
$( "a" ).attr( "href", "allMyHrefsAreTheSameNow.html" );
// 设定多个属性值
$( "a" ).attr({
title: "all titles are the same too!",
href: "somethingNew.html"
});
// 作为getter
$( "a" ).attr( "href" )
其他的方法也类似,$("a").html('sss') 设置所有 a 元素的 html 属性的值,其返回一个 jQuery 对象,可以继续链式调用方法,.end() 方法让选择重新回到原始的元素
$("a").html 获取第一个 a 元素的 html 属性的值,.text 方法例外,总是返回所有元素的值
使用选择器总是能返回一个对象,即使没有元素被选择,if( $('a.foo') ) 总是返回 true,$('a.foo').length 返回有多少元素被选中,可以将选择的元素存储在变量中 var ele = $('a.foo'),但是 DOM 改变时,jQuery 对象不会自动更新,只能重选
选择器返回的每个包装的 jQuery 对象都是唯一的。即使对象是使用相同的选择器创建的,或者包含对完全相同的 DOM 元素的引用,他们也是唯一的。即 $( "#logo" ) === $( "#logo" ) 始终返回 false,然而这两个对象包含相同的 DOM 元素,即 $( "#logo" ).get( 0 ) === $( "#logo" ).get( 0 ) 返回 true
jQuery 对象上不存在原生 DOM 方法和属性,反之亦然
精选过滤选择的元素
$( "div.foo" ).has( "p" ); // div.foo elements that contain <p> tags
$( "h1" ).not( ".bar" ); // h1 elements that don't have a class of bar
$( "ul li" ).filter( ".current" ); // unordered list items with class of current
$( "ul li" ).first(); // just the first unordered list item
$( "ul li" ).eq( 5 ); // the sixth, it return a jQuery object that can be call other method chainly
$( "ul li" ).get( 5 ); // the sixth, it return native DOM element itself, not a jQuery object.
$( "ul li" )[ 5 ]; // equivalent to $( "ul li" ).get( 5 );
$( "form :checked" ); 已选择的单选按钮或者复选框,或者是下拉框$( "h1" ).css( "fontSize" ); 返回 CSS 属性,例如 19px。
$( "h1" ).css( "fontSize", "100px" ); 设置 CSS 属性
$( "h1" ).css({ fontSize: "100px", color: "red" }); 设置多个属性,使用 .css 可以使用 fonst-size,而不需要转为驼峰格式
$.each() 用于非 jQuery 集合的普通对象、数组、类数组对象。对于 jQuery 集合,使用 .each()。
.map() 最后要跟 .get() 方法将结果收集为数组,$.map() 不需要,因为 .get() 不是原生的 JS 方法
.index() 以选择元素的父元素为依据,返回该元素在父元素子节点中的索引,以 0 为基础,选择多个元素的话默认返回第一个
// Using the core $.ajax() method
$.ajax({
// The URL for the request
url: "post.php",
// The data to send (will be converted to a query string)
data: {
id: 123
},
// Whether this is a POST or GET request
type: "GET",
// The type of data we expect back
dataType : "json",
})
// Code to run if the request succeeds (is done);
// The response is passed to the function
.done(function( json ) {
$( "<h1>" ).text( json.title ).appendTo( "body" );
$( "<div class=\"content\">").html( json.html ).appendTo( "body" );
})
// Code to run if the request fails; the raw request and
// status codes are passed to the function
.fail(function( xhr, status, errorThrown ) {
alert( "Sorry, there was a problem!" );
console.log( "Error: " + errorThrown );
console.log( "Status: " + status );
console.dir( xhr );
})
// Code to run regardless of success or failure;
.always(function( xhr, status ) {
alert( "The request is complete!" );
});
$.ajax({
url: '请求的URL',
type: '请求类型 (GET, POST, PUT, DELETE等)',
data: '发送的数据',
dataType: '预期服务器返回的数据类型',
async: true | false // 默认 true
success: function(response) {
// 请求成功后的回调函数
},
error: function(xhr, status, error) {
// 请求失败后的回调函数
}
});
// 发送文件+普通参数
var formData = new FormData();
formData.append("file", $("#file")[0].files[0]);
formData.append("userId", 1001);
formData.append("type", "image");
formData.append("ids[]", "abc");//传递数组
formData.append("ids[]", "def");
$.ajax({
url: "/upload",
type: "POST",
data: formData,
processData: false, // 不处理数据
contentType: false, // 不设置 contentType
success: function (res) {
console.log("上传成功", res);
},
error: function (err) {
console.error("上传失败", err);
}
});
// 后端接参
@PostMapping("/test")
public String test(
@RequestParam("file") MultiPartFile file,
@RequestParam("ids[]") List<String> ids,
@RequestParam("type") String type,
@RequestParam("userId") String userId
) {
System.out.println(ids);
return "ok";
}
fetch('https://api.example.com/submit', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ key1: 'value1', key2: 'value2' })
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
axios.get('https://api.example.com/data')
.then(function (response) {
console.log(response.data);
})
.catch(function (error) {
console.error(error);
});
@RequestParam接收数组:后端接收使用List<String>或String[]http://ip:port/api?ids=abc&ids=def $.ajax中写仍然可以在url中相同参数名,也可以在data中写data:{ids:'abc,def'}$.ajax的data中直接写数组:arr=['abc','def']; data:{ids:arr},则后端接收到的参数名是ids[];如果要使用ids接收,两种方式:arr=['abc','def']; data:{ids:arr.join(',')}traditional为true):arr=['abc','def']; {data:{ids:arr}, traditional: true}ids[]=abc&ids[]=def,traditional为true的序列化数组为 ids=abc&ids=def。使用$.param(arr, true)$.ajax的默认content-type为application/x-www-form-urlencoded; charset=UTF-8