语言基础

DOM、BOM、Window、Document #

DOM 是操作文档的 API,Document 是其一个对象。

BOM 是操作浏览器的 API,Window 是其一个对象。

function component(s) {
  const element = document.createElement('div');
  element.innerHTML = s;
  return element;
}

document.body.appendChild(component("hello"));

SSR vs CSR #

服务器渲染和客户端渲染

ES5、ES6、ES2021 #

两个等号与三个等号的区别 #

严格相等:===,非严格相等:==

  • 使用 == 时,如果两边的类型不一样,会先进行类型转换。

  • === 是直接进行比较,不会进行类型转换。

一般不推荐使用 == 了,几乎没有场景适用,比较 null undefined 的话可以直接显式比较,用了 == 还会被人怀疑使用的正确性,增加阅读负担。但是大于等于和小于等于仍然是 >=<=

变量声明 #

let #

let low = 0;
let low = 0, high = nums.length - 1;

var 和 let #

var 在全局级别(函数外部)进行声明会自动变成 Window对象的一个属性:

var name = "John";
window.name === name // true

let 或者 const 则不会:

let name = "John";
window.name === name // false

var 存在变量提升现象:闭包或函数内部重新声明后会对外部相同变量进行覆盖。

浅谈 JavaScript 变量提升

const #

解构赋值 #

解构赋值语法是一种 Javascript 表达式。通过解构赋值, 可以将属性/值从对象/数组中取出,赋值给其他变量。

function main() {
  l = [1, 2, 3];
  let [a, b, ...rest] = l;
  d = { hello: "world", foo: "bar", other: "other" };
  let { hello, foo, ...other } = d;
  console.log(a);
  console.log(b);
  console.log(rest);
  console.log(hello);
  console.log(foo);
  console.log(other);
}

/*
1
2
[ 3 ]
world
bar
{ other: 'other' }
*/

正则表达式 #

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Regular_Expressions

var re = /ab+c/;
// 等价于
var re = new RegExp("ab+c");

循环控制 #

JS 支持传统的 for 语法,此外还有以下 for 的用法

for … of #

for (const element of ['a', 'b', 'c']) {
  console.log(element);
}

for … in #

for...in 是为遍历对象属性而构建的,不建议与数组一起使用,数组可以用for...of

for...in 得到的是属性名称。

var obj = {a:1, b:2, c:3};

for (var prop in obj) {
  console.log("obj." + prop + " = " + obj[prop]);
}

// Output:
// "obj.a = 1"
// "obj.b = 2"
// "obj.c = 3"

do…while #

function main(params) {
    let i = 0
    do {
        console.log(i)
        i += 1
    } while (i < 10);
}

statement 执行至少一次,并在每次 condition 值为真时重新执行。

while #

程序控制 #

条件判断 #

需要注意数组越界的问题,不会报错,只会返回 undefined,然后 if 得到的结果就永远是 false

a = [1]
if(1 < a[-1]) {
    console.log("1 < a[-1]");
} else {
    console.log("1 > a[-1]");
}
// 1 > a[-1]

console.log(1 < a[-1]);
console.log(1 > a[-1]);
// false
// false

with #

严格模式下不允许使用 with 语句,否则将视为语法错误(所以能看懂就行了,尽量别用。。)

var obj = {
  host: "https://yindongliang.com",
  url: "posts",
};

with (obj) {
  host = "https://github.com";
  url = "repos";
  a = "global";
}

console.log(host); // undefined
console.log(url); // undefined
console.log(a); // global

注意:当 with 的 obj 没有某个属性却在作用域里重新声明的时候,该属性会被泄露到全局变量里。

switch #

这里有坑:省略 break 关键字会执行下一个 case

switch (expression) {
    case value: statement
	    break;
    case value: statement
	    break;
    case value: statement
    	break;
    default:
		...

try…throw…catch…finally #

function myFunction() {
  var message, x;
  message = document.getElementById("p01");
  message.innerHTML = "";
  x = document.getElementById("demo").value;
  try { 
    if(x == "") throw "值是空的";
    if(isNaN(x)) throw "值不是一个数字";
    x = Number(x);
    if(x > 10) throw "太大";
    if(x < 5) throw "太小";
  }
  catch(err) {
    message.innerHTML = "错误: " + err + ".";
  }
  finally {
    document.getElementById("demo").value = "";
  }
}

关键字 #

void 运算符 #

void 运算符 对给定的表达式进行求值,然后返回 undefined

void (async function () {
  console.log('hello void')
})();

typeof #

typeof 的对象一般是对象。

instanceof #

instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。

函数 #

普通函数 #

function showMessage() {
  alert( 'Hello everyone!' );
}

showMessage(); // 函数调用

匿名函数 #

function main(params) {
    params = {hello: "world"}
    return function f() {
        console.log("hello", params)
    }
}

箭头函数 #

在 js 中区分函数的引用和函数的执行,箭头函数返回的是函数的引用,而不是函数的执行结果。

箭头函数中 this 指的是外部的 this,因为箭头函数没有 this,匿名函数则是内部的 this。

  • 箭头函数没有 this
  • 箭头函数没有 arguments 变量
  • 不能用 new
  • 不能用 super

深入理解箭头函数

生成器函数 #

调用一个生成器函数并不会马上执行它里面的语句,而是返回一个这个生成器的 迭代器 iterator )对象。当这个迭代器的 next() 方法被首次(后续)调用时,其内的语句会执行到第一个(后续)出现yield的位置为止,yield 后紧跟迭代器要返回的值。或者如果用的是 yield*(多了个星号),则表示将执行权移交给另一个生成器函数(当前生成器暂停执行)。

事件机制 #

事件处理程序 #

事件处理程序是一个函数,且只有一个参数,那就是事件对象。

<body>
    <button >sayHi</button>
    <script>
    var b = document.querySelector("button");
    function doSomething(e){
        console.log(e.type);
        console.log("hello world");
    }
    b.onclick = doSomething;
</body>

多种方式触发事件 #

方式一

通过 onclick 属性

// html
<button>Change color</button>

// js
const btn = document.querySelector('button');

function random(number) {
  return Math.floor(Math.random()*(number+1));
}

btn.onclick = function() {
  const rndCol = 'rgb(' + random(255) + ',' + random(255) + ',' + random(255) + ')';
  document.body.style.backgroundColor = rndCol;
}

方式二

组件原生的方式。

<button onclick="bgChange()">Press me</button>

方式三

通过 addEventListener 添加事件监听器。

const btn = document.querySelector('button');

function bgChange() {
  const rndCol = 'rgb(' + random(255) + ',' + random(255) + ',' + random(255) + ')';
  document.body.style.backgroundColor = rndCol;
}

btn.addEventListener('click', bgChange);
btn.removeEventListener('click', bgChange);

事件冒泡 #

事件委托 #

严格模式 #

参考 Javascript 严格模式详解

  1. 针对整个脚本文件

在脚本文件的第一行添加

"use strict";
  1. 针对单个函数

将"use strict"放在函数体的第一行,则整个函数以"严格模式"运行。

function strict(){
	"use strict";
	return "这是严格模式。";
}
  1. 将整个脚本文件放在一个立即执行的匿名函数之中。
(function (){
	"use strict";
	// some code here
})();

类(class) #

ES6 引入了面向对象的概念(class)

static

extends

getter 和 setter #

get 语法将对象属性绑定到查询该属性时将被调用的函数。

const obj = {
  log: ['a', 'b', 'c'],
  get latest() {
    if (this.log.length === 0) {
      return undefined;
    }
    return this.log[this.log.length - 1];
  },
  set latest(value) {
    console.log(value)
  }
};

console.log(obj.latest);
// expected output: "c"
obj.latest = 1
// expected output: 1

原型链 Prototype #

JavaScript 是基于原型的语言。

没有官方的方法用于直接访问一个对象的原型对象——原型链中的“连接”被定义在一个内部属性中,在 JavaScript 语言标准中用 [[prototype]] 表示。然而,大多数现代浏览器还是提供了一个名为 __proto__ (前后各有2个下划线)的属性,其包含了对象的原型。

当我们调用一个对象的属性时,如果对象没有该属性,JavaScript 解释器就会从对象的原型对象上去找该属性,如果原型上也没有该属性,那就去找原型的原型,直到最后返回 null 为止,null 没有原型。这种属性查找的方式被称为原型链(prototype chain)。

准确地说,这些属性和方法定义在 Object 的构造器函数 (constructor functions) 之上的prototype属性上,而非对象实例本身。

__proto__(隐式原型)与 prototype(显式原型)的关系:

每个实例对象(object)都有一个私有属性(称之为 __proto__ )指向它的构造函数的原型对象(prototype)。该原型对象也有一个自己的原型对象(__proto__),层层向上直到一个对象的原型对象为 null

被构造函数创建的实例对象的 [[Prototype]] 指向 funcprototype 属性。Object.prototype 属性表示 Object 的原型对象。

__proto__每个对象都有的一个属性,而 prototype 是函数才会有的属性。

Object.setPrototypeOf #

Object.getPrototypeOf #

new 和 Object.create #

new 和 Object.create 不一样。new 包含了构造函数 constructor。

call #

bind #

apply #

Symbol #

如果我们要在对象字面量 {...} 中使用 symbol,则需要使用方括号把它括起来。

let id = Symbol("id");

let user = {
  name: "John",
  [id]: 123 // 而不是 "id":123
};

这是因为我们需要变量 id 的值作为键,而不是字符串 “id”。

https://zh.javascript.info/symbol#dui-xiang-zi-mian-liang-zhong-de-symbol

this 作用域 #

Promise,async/await #

let promise = new Promise(function(resolve, reject) {
  // executor(生产者代码)
});

Promise #

new Promise 构造器返回的 promise 对象具有以下内部属性:

  • state — 最初是 "pending",然后在 resolve 被调用时变为 "fulfilled",或者在 reject 被调用时变为 "rejected"
  • result — 最初是 undefined,然后在 resolve(value) 被调用时变为 value,或者在 reject(error) 被调用时变为 error

与最初的 “pending” promise 相反,一个 resolved 或 rejected 的 promise 都会被称为 “settled”。

then/catch/finally

async/await #

async function f() {
  return 1;
}

在函数前面的 “async” 这个单词表达了一个简单的事情:即这个函数总是返回一个 promise。其他值将自动被包装在一个 resolved 的 promise 中。

await 只在 async 函数内工作。await 实际上会暂停函数的执行,直到 promise 状态变为 settled,然后以 promise 的结果继续执行。这个行为不会耗费任何 CPU 资源,因为 JavaScript 引擎可以同时处理其他任务:执行其他脚本,处理事件等。相比于 promise.then,它只是获取 promise 的结果的一个更优雅的语法,同时也更易于读写。

module 模块 #

export+import (ES6) #

  1. 命名导出(每个模块包含任意数量)
  2. 默认导出(每个模块包含一个)

export

// 命名导出
export function hello (s) {
  return `hello, ${s}`;
}

import xxx from ‘xxx’

<!doctype html>
<script type="module">
  import {hello} from './hello.js'
  alert(hello("js"));
</script>

<script nomodule>
    alert("no module");
</script>

也可以

import * as hello from './hello.js'

nomodule

旧时的浏览器不理解 type="module", 会运行nomodule的代码块,支持 module 的浏览器不会运行该代码块。

与 Node.js 中 exports 和 required 区别

Node.js 如何处理 ES6 模块

module.exports+require (node.js) #

通过 module.exports 导出:

const car = {
  brand: 'Ford',
  model: 'Fiesta'
}

module.exports = car

在另一个文件中使用 require:

const car = require('./car')

内存管理 #

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Memory_Management

参考 #