基于mozilla Javascript教程的学习笔记

Javascript基础

What is Javascript

脚本加载策略

使其在整个页面加载完成后加载的方法:

1.对于外部脚本:添加属性type=”module”/添加defer布尔属性(async:同步,不等待页面加载完成)

2.对于内部脚本:wrap the code in a DOMContentLoaded event listener.

Variable

let​ vs var​

1.推荐使用let(后来者,更完善)

2.区别

(1)无论在哪个位置定义,var定义都会提前到代码段的最前方

(2)var允许重复定义且不会报错,若第一次定义有初值,第二次没有,变量的值不会被覆盖

常量的定义

​const​ 注:对于Object,其元素是可以修改的

Note that although a constant in JavaScript must always name the same value, you can change the content of the value that it names. This isn’t a useful distinction for simple types like numbers or booleans, but consider an object:

eg

const bird = { species: "Kestrel" };
console.log(bird.species); // "Kestrel"
bird.species = "Striated Caracara";
console.log(bird.species); // "Striated Caracara"

变量类型

Number(代表所有数,没有float与int之分,但还有Bigint) String Boolean Array Object

运算符

**​

指数(Exponent)运算,eg.5**2​值为25

===​与==​,!==​与!=​

推荐使用前者,区别:前者会检查数据类型,后者只检查值

eg:1==’1’​true 1===’1’​false

模板字符串

使用反引号(`)定义,特性:

1.可以跨多行

2.可以嵌入表达式

eg.

`string text`

`string text line 1
 string text line 2`

`string text ${expression} string text`

tagFunction`string text ${expression} string text`

3.Tag function:Tagged templates

String

注意

字符串在Javascript中为不可变的,如str[0] = ‘a’​不会生效,原字符串不会被更改,若要实现相同效果需要将后方的部分取出来和’a’拼接形成一个新的字符串

boolean .include(string)​

是否包含子串

eg.

const browserType = "mozilla";

if (browserType.includes("zilla")) {
  console.log("Found zilla!");
} else {
  console.log("No zilla here!");
}

boolean .startsWith(string) .endsWith(string)​

如题,是否以相应子串开始或结束

number .indexOf(string searchString,int position(可选))​

返回子串开始位置index(没有返回-1)

eg.

const tagline = "MDN - Resources for developers, by developers";
console.log(tagline.indexOf("x")); // -1
const firstOccurrence = tagline.indexOf("developers");
const secondOccurrence = tagline.indexOf("developers", firstOccurrence + 1);

console.log(firstOccurrence); // 20
console.log(secondOccurrence); // 35

string .slice(number,number(可选))​

第一个参数为开始位置,第二个参数为结束位置,不填写则取到字符串结束,填写负数则表示从后往前数

eg.

const browserType = "mozilla";
console.log(browserType.slice(1, 4)); // "ozi"

string .toLowerCase()/.toUpperCase()​

返回转小写/大写后的字符串(不会改变原来的字符串)

string .replace(string,string)​

将字符串中第一个出现的前者替换为后者(不改变原来的字符串)

string .replaceAll(string,string)​

改变所有

string .charAt(number)​

取相应位置的字符

Array

number .indexOf(any)​

如题,取某个对象的index

number .push(any)​

添加元素到数组末尾,返回值为新数组长度

number .unshift(any)​

添加元素到数组首,返回值为新数组长度

array .concat(value1…valuen)​

连接数组/元素到末端,返回新数组(不会改变原数组,与push区别)

any .shift()​

删除数组尾元素并返回该元素

any .pop()​

删除数组首元素并返回该元素

array .splice(number,number)​

删除第一个参数开始第二个参数个的元素并返回这些元素组成的数组

遍历数组

for(const(let也可) item of array){}​

item改变不会导致原数组相应内容改变

array .map(callbackFn)​

对每个元素执行相同操作后返回新的数组(函数的三个参数:element index array)

array .filter(callbackFn)​

筛选出函数返回为真的元素并返回这些元素组成的数组(函数的三个参数:element index array)

array .flat(number depth)​

将数组展平(解除多层嵌套)

eg.

const arr1 = [0, 1, 2, [3, 4]];

console.log(arr1.flat());
// expected output: Array [0, 1, 2, 3, 4]

const arr2 = [0, 1, [2, [3, [4, 5]]]];

console.log(arr2.flat());
// expected output: Array [0, 1, 2, Array [3, Array [4, 5]]]

console.log(arr2.flat(2));
// expected output: Array [0, 1, 2, 3, Array [4, 5]]

console.log(arr2.flat(Infinity));
// expected output: Array [0, 1, 2, 3, 4, 5]

string .join(string(可选,默认为”,”))​

把所有元素拼接在一起,以参数分隔,成为一个新的字符串(非字符串元素会转换为字符串)

string .toString()​

把所有元素拼接在一起,以逗号分隔

conditionals

switch

switch (expression) {
  case choice1:
    // run this code
    break;

  case choice2:
    // run this code instead
    break;

  // include as many cases as you like

  default:
    // actually, just run this code
    break;
}

function

匿名函数,箭头函数

当用于回调函数时,常常使用箭头函数(某种程度上是匿名函数的替代)

eg.

textBox.addEventListener("keydown", function (event) {
  console.log(`You pressed "${event.key}".`);
});	//匿名函数

textBox.addEventListener("keydown", (event) => {
  console.log(`You pressed "${event.key}".`);
});	//箭头函数

const originals = [1, 2, 3];
const doubled = originals.map(item => item * 2);
console.log(doubled); // [2, 4, 6]
//return也有时可以省略

Event

none .addEventListener(string,callbackFn)​

回调函数的参数为event

none .removeEventHandler(string,callbackFn)​

移除回调函数为第二个参数的第一参数类型的eventlistener

AbortSignal——另一种移除listener的方法

const controller = new AbortController();

btn.addEventListener("click",
  () => {
    const rndCol = `rgb(${random(255)} ${random(255)} ${random(255)})`;
    document.body.style.backgroundColor = rndCol;
  },
  { signal: controller.signal } // pass an AbortSignal to this handler
);

controller.abort(); // removes any/all event handlers associated with this controller

Event Object

.target​

指向event触发主体(直接触发主体)

.currentTarget​

指向当前冒泡冒到的主体(handler所绑定的主体)

.preventDefault()​

阻止默认行为(submit使用居多)

.stopPropagation()​

停止事件传播

Event Capture

event默认行为:冒泡(bubbling),从内层元素到外层元素触发

capture:反方向,从外到内触发

设置方法

pass the capture​ option in addEventListener()​

eg.

const output = document.querySelector("#output");
function handleClick(e) {
  output.textContent += `You clicked on a ${e.currentTarget.tagName} element\n`;
}

const container = document.querySelector("#container");
const button = document.querySelector("button");

document.body.addEventListener("click", handleClick, { capture: true });
container.addEventListener("click", handleClick, { capture: true });
button.addEventListener("click", handleClick);

Object

像是class与dict的糅合体

Object literal

通过写出objectd的内容创建对象(成员可以是函数

const person = {
  name: ["Bob", "Smith"],
  age: 32,
  bio() {
    console.log(`${this.name[0]} ${this.name[1]} is ${this.age} years old.`);
  },
  introduceSelf() {
    console.log(`Hi! I'm ${this.name[0]}.`);
  },
};

访问方式

1.通过点访问

name.first;
name.last;

2.通过中括号访问

person.age;
person.name.first;

Constructor

像创建函数一样创建类

eg

function Person(name) {
  this.name = name;
  this.introduceSelf = function () {
    console.log(`Hi! I'm ${this.name}.`);
  };
}

const salva = new Person("Salva");
salva.introduceSelf();
// "Hi! I'm Salva."

const frankie = new Person("Frankie");
frankie.introduceSelf();
// "Hi! I'm Frankie."

Prototype

类似继承与多态,prototype即相当于object的父类

object默认的prototype为Object.prototype,也是最基本的prototype

当获取Object的成员时,会沿着prototype一路向上搜索,称为prototype chain,直到搜索到第一个满足条件的成员(类似多态)或者prototype为null为止

object Object.getPrototypeOf(object)​

返回object的prototype

设置object的prototype

法1. object Object.create(object)​,参数为prototype

法2.设置constructor的prototype,之后该constructor创建的所有object都会具有相应的prototype

eg.

const personPrototype = {
  greet() {
    console.log(`hello, my name is ${this.name}!`);
  },
};

function Person(name) {
  this.name = name;
}

Object.assign(Person.prototype, personPrototype);
// or
// Person.prototype.greet = personPrototype.greet;

Own Properties

指直接由本层构造器定义的property

判断是否为own property:

eg.

const irma = new Person("Irma");

console.log(Object.hasOwn(irma, "name")); // true
console.log(Object.hasOwn(irma, "greet")); // false

与传统OOP(面向对象)继承的差别

实际上同时实例化的多个对象,通过prototype连接在一起,与其说是继承不如说是“代理/委托”(delegation)

更加传统的OOP方法——class

eg.

class Student extends Person {
  #year;

  constructor(name, year) {
    super(name);
    this.#year = year;
  }

  introduceSelf() {
    console.log(`Hi! I'm ${this.name}, and I'm in year ${this.#year}.`);
  }

  canStudyArchery() {
    return this.#year > 1;
  }
}

底层仍然使用object实现(仍然是prototype的方式实现继承),但多了把函数/对象变成私有的方法:在前面加#​

DOM

Window,navigator & document

image

WIndow代表加载页面的浏览器窗口,The window is the browser tab that a web page is loaded into;this is represented in JavaScript by the Window​ object. Using methods available on this object you can do things like return the window’s size (see Window.innerWidth​ and Window.innerHeight​), manipulate the document loaded into that window, store data specific to that document on the client-side (for example using a local database or other storage mechanism), attach an event handler to the current window, and more.

Navigator代表浏览器The navigator represents the state and identity of the browser (i.e. the user-agent) as it exists on the web. In JavaScript, this is represented by the Navigator​ object. You can use this object to retrieve things like the user’s preferred language, a media stream from the user’s webcam, etc.

Document则为实际加载的页面The document (represented by the DOM in browsers) is the actual page loaded into the window, and is represented in JavaScript by the Document​ object. You can use this object to return and manipulate information on the HTML and CSS that comprises the document, for example get a reference to an element in the DOM, change its text content, apply new styles to it, create new elements and add them to the current element as children, or even delete it altogether.

Node

object document.createElement(string)​

参数为tagname,创建相应标签(节点)

none .appendChild(object)​

在最后添加新的子元素

object document.createTextNode(string)

创建纯文字节点

none .removeChild(object)​

删除相应的孩子节点

none .remove()​

删除自身

也可以使用linkPara.parentNode.removeChild(linkPara)​代替

使用Dom编辑style

style名称使用小驼峰命名法代替横线

none .setAttribute(string attributeName,string value)​

设置属性值

异步Javascript

两重实现异步程序的形式:

1.callback

弊端:多层嵌套,难以处理错误,需要在每一层处理错误,难以理解

eg.

function doStep1(init, callback) {
  const result = init + 1;
  callback(result);
}

function doStep2(init, callback) {
  const result = init + 2;
  callback(result);
}

function doStep3(init, callback) {
  const result = init + 3;
  callback(result);
}

function doOperation() {
  doStep1(0, (result1) => {
    doStep2(result1, (result2) => {
      doStep3(result2, (result3) => {
        console.log(`result: ${result3}`);
      });
    });
  });
}

doOperation();

2.promise

Promise

Promise状态

  • pending(待定):promise正在进行中
  • settled(已敲定):promise已执行完成
    • fulfilled(已兑现(resolve)):异步行为成功执行
    • rejected(已拒绝):异步函数出错

Promise处理

链式处理

对于Promise对象:

  • ​.then(callbackFn)​表示fulfilled后的行为,此callbackFn可再返回一个Promise,可以继续then形成一条链条eg.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); });
  • ​.catch(callbackFn)​表示rejected后的行为,注意:一条链上的任何Promise抛出错误都会由其处理,某种程度上解决了异步函数异常处理嵌套问题eg.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 error: ${response.status}`); } return response.json(); }) .then((data) => { console.log(data[0].name); }) .catch((error) => { console.error(`Could not get products: ${error}`); });

使用async/await函数处理(复古风)

构建一个async异步函数,在内部try catch,并await执行promise,此时promise被await修饰后直接返回fulfill时的值(被resolve),并与该函数同步执行,可直接顺序写下一步

eg.

async function fetchProducts() {
  try {
    // after this line, our function will wait for the `fetch()` call to be settled
    // the `fetch()` call will either return a Response or throw an error
    const response = await fetch(
      "https://mdn.github.io/learning-area/javascript/apis/fetching-data/can-store/products.json",
    );
    if (!response.ok) {
      throw new Error(`HTTP error: ${response.status}`);
    }
    // after this line, our function will wait for the `response.json()` call to be settled
    // the `response.json()` call will either return the parsed JSON object or throw an error
    const data = await response.json();
    console.log(data[0].name);
  } catch (error) {
    console.error(`Could not get products: ${error}`);
  }
}

fetchProducts();

注意,async修饰的函数返回Promise对象,其fulfilled后的值(then里调用回调函数处理的)为函数的返回值,rejected则为throw error的情况

多个Promise同步执行互不依赖但使用同一then处理

promise Promise.all(array)​全部成功才算成功(then传入的值为list)

eg.

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(`Failed to fetch: ${error}`);
  });

promise Promise.any(array)​一个成功即处理(then传入的值为单个)

eg.

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.any([fetchPromise1, fetchPromise2, fetchPromise3])
  .then((response) => {
    console.log(`${response.url}: ${response.status}`);
  })
  .catch((error) => {
    console.error(`Failed to fetch: ${error}`);
  });

Promise的创建

​promise Promise(function(resolve,reject))​

作为参数的函数其参数内容为两个函数指针,其内容则为promise需要实现的内容,若成功执行则调用resolve函数(参数为作为then参数的返回值),失败则调用reject(error),抛出错误也视为reject

eg.

function alarm(person, delay) {
  return new Promise((resolve, reject) => {
    if (delay < 0) {
      reject(new Error("Alarm delay must not be negative"));
      return;
    }
    setTimeout(() => {
      resolve(`Wake up, ${person}!`);
    }, delay);
  });
}

Worker

为Javascript提供了多线程的可能。

  1. 单独创建一个文件作为另一个线程的内容
  1. 主线程与worker采用message沟通,且worker无法访问DOM,以及主线程的任何内容
  2. eg.main.js// Create a new worker, giving it the code in "generate.js" const worker = new Worker("./generate.js"); // When the user clicks "Generate primes", send a message to the worker. // The message command is "generate", and the message also contains "quota", // which is the number of primes to generate. document.querySelector("#generate").addEventListener("click", () => { const quota = document.querySelector("#quota").value; worker.postMessage({ command: "generate", quota, }); }); // When the worker sends a message back to the main thread, // update the output box with a message for the user, including the number of // primes that were generated, taken from the message data. worker.addEventListener("message", (message) => { document.querySelector("#output").textContent = `Finished generating ${message.data} primes!`; }); document.querySelector("#reload").addEventListener("click", () => { document.querySelector("#user-input").value = 'Try typing in here immediately after pressing "Generate primes"'; document.location.reload(); });

worker.js

// Listen for messages from the main thread.
// If the message command is "generate", call `generatePrimes()`
addEventListener("message", (message) => {
  if (message.data.command === "generate") {
    generatePrimes(message.data.quota);
  }
});

// Generate primes (very inefficiently)
function generatePrimes(quota) {
  function isPrime(n) {
    for (let c = 2; c <= Math.sqrt(n); ++c) {
      if (n % c === 0) {
        return false;
      }
    }
    return true;
  }

  const primes = [];
  const maximum = 1000000;

  while (primes.length < quota) {
    const candidate = Math.floor(Math.random() * (maximum + 1));
    if (isPrime(candidate)) {
      primes.push(candidate);
    }
  }

  // When we have finished, send a message to the main thread,
  // including the number of primes we generated.
  postMessage(primes.length);
}

JS构建网络请求

The FetchAPI

fetch会返回promise,只要网络没有错误,无论HTTP状态码,均视为fulfilled,故需要判断response.ok

response.json() response.text()等均返回Promise,需要继续then处理

eg.

// Call `fetch()`, passing in the URL.
fetch(url)
  // fetch() returns a promise. When we have received a response from the server,
  // the promise's `then()` handler is called with the response.
  .then((response) => {
    // Our handler throws an error if the request did not succeed.
    if (!response.ok) {
      throw new Error(`HTTP error: ${response.status}`);
    }
    // Otherwise (if the response succeeded), our handler fetches the response
    // as text by calling response.text(), and immediately returns the promise
    // returned by `response.text()`.
    return response.text();
  })
  // When response.text() has succeeded, the `then()` handler is called with
  // the text, and we copy it into the `poemDisplay` box.
  .then((text) => {
    poemDisplay.textContent = text;
  })
  // Catch any errors that might happen, and display a message
  // in the `poemDisplay` box.
  .catch((error) => {
    poemDisplay.textContent = `Could not fetch verse: ${error}`;
  });

构建请求

constructor可选参数中,包含method、headers等,例子:

const response = await fetch("https://example.org/post", {
  method: "POST",
  headers: {
    "Content-Type": "application/x-www-form-urlencoded",
  },
  // Automatically converted to "username=example&password=password"
  body: new URLSearchParams({ username: "example", password: "password" }),
  // ...
});

const response = await fetch("https://example.org/post", {
  method: "POST",
  body: JSON.stringify({ username: "example" }),
  // ...
});

使用request与克隆请求

eg.

const request1 = new Request("https://example.org/post", {
  method: "POST",
  body: JSON.stringify({ username: "example" }),
});

const request2 = request1.clone();

const response1 = await fetch(request1);
console.log(response1.status);

const response2 = await fetch(request2);
console.log(response2.status);

response.blob()​

对于图片类二进制文件,使用该方法,处理时,URL.createObjectURL(blob)​会生成该图片的blob url(可以当做普通url使用)

Blob – Web APIs | MDN

跨域

Whether a request can be made cross-origin or not is determined by the value of the RequestInit.mode​ option. This may take one of three values: cors​, same-origin​, or no-cors​.

  • For fetch requests the default value of mode​ is cors​, meaning that if the request is cross-origin then it will use the Cross-Origin Resource Sharing (CORS) mechanism. This means that:
    • if the request is a simple request, then the request will always be sent, but the server must respond with the correct Access-Control-Allow-Origin​ header or the browser will not share the response with the caller.
    • if the request is not a simple request, then the browser will send a preflighted request to check that the server understands CORS and allows the request, and the real request will not be sent unless the server responds to the preflighted request with the appropriate CORS headers.
  • Setting mode​ to same-origin​ disallows cross-origin requests completely.
  • Setting mode​ to no-cors​ disables CORS for cross-origin requests. This restricts the headers that may be set, and restricts methods to GET, HEAD, and POST. The response is opaque, meaning that its headers and body are not available to JavaScript. Most of the time a website should not use no-cors​: the main application of it is for certain service worker use cases.

XMLHttpRequest

const request = new XMLHttpRequest();

try {
  request.open("GET", "products.json");

  request.responseType = "json";

  request.addEventListener("load", () => initialize(request.response));
  request.addEventListener("error", () => console.error("XHR error"));

  request.send();
} catch (error) {
  console.error(`XHR error ${request.status}`);
}

There are five stages to this:

  1. Create a new XMLHttpRequest​ object.
  2. Call its open()​ method to initialize it.
  3. Add an event listener to its load​ event, which fires when the response has completed successfully. In the listener we call initialize()​ with the data.
  4. Add an event listener to its error​ event, which fires when the request encounters an error
  5. Send the request.

We also have to wrap the whole thing in the try…catch block, to handle any errors thrown by open()​ or send()​.

Hopefully you think the Fetch API is an improvement over this. In particular, see how we have to handle errors in two different places.

闭包(closure)

类似于类中封装私有变量,外部不可访问,只能通过函数访问

定义:闭包是指 函数能够“捕获”并记住其定义时所在的作用域,即使在该作用域外调用函数,仍能访问该作用域中的变量。

例子:

const counter = (function () {
  let privateCounter = 0;
  function changeBy(val) {
    privateCounter += val;
  }

  return {
    increment() {
      changeBy(1);
    },

    decrement() {
      changeBy(-1);
    },

    value() {
      return privateCounter;
    },
  };
})();

console.log(counter.value()); // 0.

counter.increment();
counter.increment();
console.log(counter.value()); // 2.

counter.decrement();
console.log(counter.value()); // 1.

可以嵌套

eg.

// global scope
const e = 10;
function sum(a) {
  return function (b) {
    return function (c) {
      // outer functions scope
      return function (d) {
        // local scope
        return a + b + c + d + e;
      };
    };
  };
}

console.log(sum(1)(2)(3)(4)); // 20

// global scope
const e = 10;
function sum(a) {
  return function sum2(b) {
    return function sum3(c) {
      // outer functions scope
      return function sum4(d) {
        // local scope
        return a + b + c + d + e;
      };
    };
  };
}

const sum2 = sum(1);
const sum3 = sum2(2);
const sum4 = sum3(3);
const result = sum4(4);
console.log(result); // 20

常见错误:var定义变量不会对大括号作用域生效,故所有item共享一个作用域,后面内容被覆盖

function showHelp(help) {
  document.getElementById("help").textContent = help;
}

function setupHelp() {
  var helpText = [
    { id: "email", help: "Your email address" },
    { id: "name", help: "Your full name" },
    { id: "age", help: "Your age (you must be over 16)" },
  ];

  for (var i = 0; i < helpText.length; i++) {
    // Culprit is the use of `var` on this line
    var item = helpText[i];
    document.getElementById(item.id).onfocus = function () {
      showHelp(item.help);
    };
  }
}

setupHelp();

解决方法1:每个函数创建新的闭包,变量被复制到新闭包作用域中使用

function showHelp(help) {
  document.getElementById("help").textContent = help;
}

function makeHelpCallback(help) {
  return function () {
    showHelp(help);
  };
}

function setupHelp() {
  var helpText = [
    { id: "email", help: "Your email address" },
    { id: "name", help: "Your full name" },
    { id: "age", help: "Your age (you must be over 16)" },
  ];

  for (var i = 0; i < helpText.length; i++) {
    var item = helpText[i];
    document.getElementById(item.id).onfocus = makeHelpCallback(item.help);
  }
}

setupHelp();

解决方法2.使用let定义变量(对大括号生效)

function showHelp(help) {
  document.getElementById("help").textContent = help;
}

function setupHelp() {
  const helpText = [
    { id: "email", help: "Your email address" },
    { id: "name", help: "Your full name" },
    { id: "age", help: "Your age (you must be over 16)" },
  ];

  for (let i = 0; i < helpText.length; i++) {
    const item = helpText[i];
    document.getElementById(item.id).onfocus = () => {
      showHelp(item.help);
    };
  }
}

setupHelp();

并发模型(Concurrency Model)与事件循环(Event Loop)

AI讲得好好…1

同步代码被立即压入stack中,当stack执行完成时,event loop会从queue中取message,将其回调函数压入stack中,stack执行完毕后重复该过程

在执行message的过程中,微任务会比宏任务先执行

微任务(Microtasks)

微任务的优先级较高,会在当前的 宏任务执行结束后,立即执行。在事件循环中,如果有微任务待处理,微任务会在进入下一个宏任务之前完成。

常见的微任务包括:

  • ​Promise.then​ 或 Promise.catch​ 的回调。
  • ​MutationObserver​。
  • ​queueMicrotask​(显式创建微任务的方法)。

宏任务(Macro tasks)

宏任务的优先级低于微任务。事件循环每次循环会取出一个宏任务执行,然后处理所有的微任务,接着再进入下一个宏任务。

常见的宏任务包括:

  • ​setTimeout​
  • ​setInterval​
  • ​setImmediate​(Node.js 中)
  • ​I/O 操作​
  • ​UI 渲染​(浏览器环境中)

JS模块

模块js文件中采用export导出需要被导入的对象(可以在模块末尾集中导出或者在需要导出的函数/对象前添加export关键词)

eg.

function create(id, parent, width, height) {
  ...
}

function createReportList(wrapperId) {
  ...
}

export { create, createReportList };

或者

export const name = "square";

export function draw(ctx, length, x, y, color) {
  ctx.fillStyle = color;
  ctx.fillRect(x, y, length, length);

  return { length, x, y, color };
}

主js文件(直接嵌入html的)通过import导入

eg.

import { name, draw, reportArea, reportPerimeter } from "./modules/square.js";

特性:

默认导出

一个模块文件只能存在一个

eg.

export default function (ctx) {
  // ...
}

导入:

import { default as randomSquare } from "./modules/square.js";

或者

import randomSquare from "./modules/square.js";

导入与导出的重命名

eg.

// module.js 中
export { function1 as newFunctionName, function2 as anotherNewFunctionName };

// main.js 中
import { newFunctionName, anotherNewFunctionName } from "/modules/module.js";

// module.js 中
export { function1, function2 };

// main.js 中
import {
  function1 as newFunctionName,
  function2 as anotherNewFunctionName,
} from "./modules/module.js";

模块对象

将模块功能导入到一个模块功能对象中

eg.

import * as Module from "/modules/module.js";

使用时

Module.function1();
Module.function2();

合并模块

eg.

shape.js

export { Square } from "./shapes/square.js";
export { Triangle } from "./shapes/triangle.js";
export { Circle } from "./shapes/circle.js";

main.js

import { Square, Circle, Triangle } from "./modules/shapes.js";

动态加载模块

导入声明是提升的

循环导入

使用导入映射导入模块

例如,下面导入映射中的 imports​ 键定义了一个“模块标识符映射”JSON 对象,其中属性名称可以用作模块标识符,当浏览器解析模块 URL 时,相应的值将被替换。这些值必须是绝对或相对 URL。使用文档包含导入映射的基础 URL 将相对 URL 解析为绝对 URL。

<script type="importmap">
  {
    "imports": {
      "shapes": "./shapes/square.js",
      "shapes/square": "./modules/shapes/square.js",
      "https://example.com/shapes/square.js": "./shapes/square.js",
      "https://example.com/shapes/": "/shapes/square/",
      "../shapes/square": "./shapes/square.js"
    }
  }
</script>

然后

// 裸模块名称作为模块标识符
import { name as squareNameOne } from "shapes";
import { name as squareNameTwo } from "shapes/square";

// 重新映射一个 URL 到另一个 URL
import { name as squareNameThree } from "https://example.com/shapes/square.js";
// 重新映射一个 URL 作为前缀 ( https://example.com/shapes/)
import { name as squareNameFour } from "https://example.com/shapes/moduleshapes/square.js";

在导入映射中,可能有多个键可以匹配一个模块标识符。例如,模块标识符 shapes/circle/​ 可以匹配模块标识符键 shapes/​ 和 shapes/circle/​。在这种情况下,浏览器将选择最具体(最长)的匹配模块标识符键。

ES6重点

Set

新的数据结构,用于去重

eg.

const uniqueNumbers = new Set([1, 2, 2, 3]);
console.log(uniqueNumbers); // Set {1, 2, 3}

Map

键值对存储(哈希表查找,查找方便,key可以是object或任何类型,不一定是number)

Map vs Object

适用场景使用Map​使用Object​
键类型不确定(对象、函数、数值等)❌ 只能用字符串
频繁增删键值对✅O(1)​查找❌O(n)​查找
需要按插入顺序存储❌ 顺序不固定
遍历键值对✅ 直接for…of​❌ 需Object.entries()​
大小管理(.size)✅ 直接map.size​❌ 需Object.keys().length​
简单的键值存储(少量数据)⛔Map​适合大量数据✅Object​更轻量
兼容性(旧版 JS 环境)❌ 可能不兼容老浏览器✅ 所有 JS 版本支持

扩展运算符…​

用途扩展运算符(
数组复制​const copy = […arr]​
数组合并​const merged = […arr1, …arr2]​
字符串转换数组​const chars = […”hello”]​
对象复制​const copy = { …obj }​
对象合并​const merged = { …obj1, …obj2 }​
对象更新​const newObj = { …obj, key: newValue }​
删除对象属性​const { key, …rest } = obj;​
函数参数展开​sum(…arr)​
剩余参数​function fn(…args) {}​

扩展运算符(Spread Operator,

扩展运算符 (…​) 是 ES6(ES2015)新增的语法,用于展开数组、对象或函数参数,使代码更加简洁、高效。


1. 数组中的扩展运算符

扩展运算符可以展开(解构)数组,用于复制、合并、转换数组等操作。

🔹 1.1 复制数组

扩展运算符可用于创建数组的 浅拷贝(不会影响原数组)。

const arr1 = [1, 2, 3];
const arr2 = [...arr1]; // 复制数组
arr2.push(4);

console.log(arr1); // [1, 2, 3](原数组未受影响)
console.log(arr2); // [1, 2, 3, 4]

🔹 1.2 合并数组

使用扩展运算符可以轻松合并多个数组,避免使用 concat()​ 方法:

const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];

const merged = [...arr1, ...arr2];
console.log(merged); // [1, 2, 3, 4, 5, 6]

 concat()​ 方法对比:

const merged2 = arr1.concat(arr2);
console.log(merged2); // [1, 2, 3, 4, 5, 6]

但使用 …​ 更简洁、更直观


🔹 1.3 数组转换

可以将 字符串或 NodeList​ 等可迭代对象转换为数组

const str = "hello";
const chars = [...str]; // ['h', 'e', 'l', 'l', 'o']
console.log(chars);

对于 NodeList​:

const divs = document.querySelectorAll("div");
const divArray = [...divs]; // 转换为数组,便于使用数组方法
console.log(divArray);

2. 对象中的扩展运算符

在 对象字面量({} 中,扩展运算符可以用于复制对象、合并对象或更新对象属性

🔹 2.1 复制对象

扩展运算符可以浅拷贝对象(不会拷贝嵌套对象):

const obj1 = { name: "Alice", age: 25 };
const obj2 = { ...obj1 }; // 复制对象

obj2.age = 30;
console.log(obj1); // { name: "Alice", age: 25 }
console.log(obj2); // { name: "Alice", age: 30 }(不会影响原对象)

🔹 2.2 合并对象

扩展运算符可用于合并多个对象:

const obj1 = { name: "Alice" };
const obj2 = { age: 25 };
const merged = { ...obj1, ...obj2 };

console.log(merged); // { name: "Alice", age: 25 }

当两个对象有相同的键时,后面的值会覆盖前面的:

const obj1 = { name: "Alice", age: 20 };
const obj2 = { age: 25, city: "New York" };

const merged = { ...obj1, ...obj2 };
console.log(merged); // { name: "Alice", age: 25, city: "New York" }

合并顺序很重要! 后面的 obj2​ 覆盖了 obj1​ 的 age​。


🔹 2.3 添加或更新对象属性

扩展运算符可以创建新对象,同时修改或添加属性

const user = { name: "Alice", age: 25 };
const updatedUser = { ...user, age: 30, city: "New York" };

console.log(updatedUser); // { name: "Alice", age: 30, city: "New York" }

这样不会修改原对象,而是创建了一个新对象


🔹 2.4 删除对象属性

JavaScript 目前没有 delete​ 以外的删除对象属性的方法,但可以用扩展运算符和 解构赋值 来实现:

const user = { name: "Alice", age: 25, city: "New York" };
const { age, ...rest } = user;

console.log(rest); // { name: "Alice", city: "New York" }

这样 age​ 被排除,rest​ 存放了剩下的属性。


3. 函数中的扩展运算符

扩展运算符可以用于函数参数,用于传递可变数量的参数

🔹 3.1 传递多个参数
function sum(x, y, z) {
  return x + y + z;
}

const numbers = [1, 2, 3];
console.log(sum(...numbers)); // 6

比 sum(numbers[0], numbers[1], numbers[2])​ 更简洁。


🔹 3.2 结合 Math​ 方法
const nums = [10, 20, 5, 8];
console.log(Math.max(...nums)); // 20
console.log(Math.min(...nums)); // 5

 apply()​ 方法对比:

console.log(Math.max.apply(null, nums)); // 20

​…​ 方式更直观、易读。


🔹 3.3 剩余参数(Rest Parameters)

扩展运算符也可以用于函数参数,将多个参数合并成数组

function collect(...items) {
  console.log(items);
}

collect(1, 2, 3, 4); // [1, 2, 3, 4]

这样可以接收任意数量的参数。

示例:计算多个数的和

function sumAll(...nums) {
  return nums.reduce((sum, num) => sum + num, 0);
}

console.log(sumAll(1, 2, 3, 4, 5)); // 15

4. 扩展运算符 vs Object.assign()

🔹 使用 Object.assign()
const obj1 = { name: "Alice" };
const obj2 = { age: 25 };

const merged = Object.assign({}, obj1, obj2);
console.log(merged); // { name: "Alice", age: 25 }

问题: 语法繁琐,必须传入 {}​。

🔹 使用扩展运算符
const merged = { ...obj1, ...obj2 };

更简洁、更现代



  1. JavaScript 事件循环(Event Loop)详解JavaScript 是 单线程 语言,无法同时执行多个任务。但为了避免同步代码(如 while(true)​ 死循环)阻塞整个页面,它使用了一种 事件驱动、异步非阻塞的机制 —— 事件循环(Event Loop),用于管理和执行异步代码。事件循环的基本工作原理JavaScript 代码执行时,会涉及以下几个关键部分:
    1. 调用栈(Call Stack)
      • 用于执行同步代码,遵循 LIFO(后进先出) 规则。
    2. Web APIs(浏览器环境提供的异步 API)
      • 如 setTimeout​、DOM 事件​、fetch​、Promise​ 等。
    3. 消息队列(Callback Queue)
      • 用于存放 宏任务(macro-task),如 setTimeout​、setInterval​、setImmediate​、I/O​、UI 渲染​ 事件等。
    4. 微任务队列(Microtask Queue)
      • 用于存放 微任务(micro-task),如 Promise.then()​、queueMicrotask()​、MutationObserver​。
    5. 事件循环(Event Loop)
      • 负责协调 调用栈、消息队列、微任务队列,按照一定的规则执行代码。
    事件循环的执行顺序1. 执行同步代码(同步任务)所有同步代码(普通函数调用、变量声明等)会直接进入 调用栈 依次执行。2. 处理微任务(Microtasks)每次执行完同步代码后,事件循环会检查 微任务队列,如果有微任务,依次执行所有微任务,直到微任务队列清空。3. 处理宏任务(Macrotasks)如果微任务队列为空,事件循环会:
    • 从宏任务队列(如 setTimeout​ 回调)中取出最早的一个任务,放入 调用栈 执行。
    • 执行完后,再检查微任务队列,如果有微任务,执行所有微任务。
    • 依此类推,不断重复这个循环。
    示例 1:基础事件循环console.log('A'); setTimeout(() => { console.log('B'); }, 0); Promise.resolve().then(() => { console.log('C'); }); console.log('D'); 执行顺序解析
    1. ​’A’​ 直接在调用栈执行(同步)。
    2. ​setTimeout​ 被调用,注册回调 () => console.log(‘B’)​,放入宏任务队列(不会立即执行)。
    3. ​Promise.resolve().then(…)​ 里的回调 () => console.log(‘C’)​ 放入微任务队列
    4. ​’D’​ 直接执行(同步)。
    5. 执行所有微任务,输出 ‘C’​。
    6. 执行宏任务(setTimeout​ 回调),输出 ‘B’​。
    最终输出顺序:A D C B 示例 2:多个 setTimeout​  Promise​ 交错console.log('1'); setTimeout(() => { console.log('2'); }, 0); Promise.resolve().then(() => { console.log('3'); return Promise.resolve(); }).then(() => { console.log('4'); }); console.log('5'); 执行顺序解析
    1. ​’1’​ 直接执行(同步)。
    2. ​setTimeout​ 注册回调 () => console.log(‘2’)​,进入宏任务队列
    3. ​Promise.then()​ 的回调 () => console.log(‘3’)​,进入微任务队列
    4. ​’5’​ 直接执行(同步)。
    5. 执行所有微任务
      • 执行 ‘3’​,然后 return Promise.resolve()​,触发新的 .then()​,其回调 () => console.log(‘4’)​ 也进入微任务队列
      • 继续执行 ‘4’​。
    6. 执行宏任务(setTimeout​ 回调),输出 ‘2’​。
    最终输出顺序:1 5 3 4 2 微任务 vs. 宏任务(重点区别)任务类型例子何时执行宏任务(Macrotask)​setTimeout​、setInterval​、setImmediate​(Node.js)、I/O​、UI 渲染​事件循环的每个循环中,取出一个宏任务执行,执行完后,再检查微任务队列。微任务(Microtask)​Promise.then()​、queueMicrotask()​、MutationObserver​每次同步任务结束后,立即执行所有微任务,确保微任务队列清空。示例 3:微任务优先级setTimeout(() => console.log('setTimeout'), 0); Promise.resolve().then(() => console.log('Promise1')); queueMicrotask(() => console.log('Microtask')); console.log('Synchronous'); 执行顺序
    1. ​’Synchronous’​ 直接执行。
    2. ​Promise.then()​ 放入微任务队列。
    3. ​queueMicrotask()​ 也是微任务,加入微任务队列。
    4. 先执行所有微任务
      • ​’Promise1’​
      • ​’Microtask’​
    5. 事件循环进入下一轮,执行宏任务(setTimeout​)。
    6. 输出 ‘setTimeout’​。
    最终输出:Synchronous Promise1 Microtask setTimeout 事件循环在 Node.js 中的不同在 Node.js,事件循环有更复杂的六个阶段
    1. timers 阶段(执行 setTimeout​、setInterval​)。
    2. I/O callbacks 阶段(执行 I/O 相关回调)。
    3. idle, prepare(Node.js 内部使用)。
    4. poll(等待 I/O 事件)。
    5. check(执行 setImmediate​)。
    6. close callbacks(执行 socket.on(‘close’, …)​ 等)。
    但在执行顺序上,微任务(Promise、process.nextTick)始终优先,这与浏览器的事件循环类似。总结
    1. JavaScript 是单线程的,但借助**事件循环(Event Loop)**管理异步任务。
    2. 同步任务 直接在 调用栈 执行。
    3. 异步任务 分为 宏任务(Macrotask) 和 微任务(Microtask)
    4. 微任务比宏任务优先执行,每次同步任务执行完后,会先执行所有微任务,然后再执行宏任务队列中的下一个任务。
    5. 常见的微任务:Promise.then()​、queueMicrotask()​、MutationObserver​。
    6. 常见的宏任务:setTimeout()​、setInterval()​、setImmediate()​(Node.js)、I/O 任务、UI 渲染。
    面试高频考点
    • ​setTimeout​ vs. Promise.then()​ 谁先执行?
    • 多个 Promise.then()​ 嵌套时的执行顺序?
    • Node.js 事件循环 vs. 浏览器事件循环的区别?
    • ​queueMicrotask()​ 和 Promise.then()​ 的区别?
    你有具体的代码想测试或不理解的点吗?😊