JavaScript 基础知识
回答一些不是很具体的问题(问题比较泛):说的全不全面,反映出不同的水平
JS 基本类型
- undefined
- null
- Nubmer(float, double 双精度浮点数)
- String
- Symbol(值类型)
- BigInt
- boolean
TS 基本类型
- TS 是 JS 超集,所以 JS 有的,TS 也有
- enum
- Tuple
- Array
- Object
- never
- any
- void
ES3
常见的类型转换
console.log(Number(NaN)); // NaN
console.log(Number(null)); // 0
console.log(Number(undefined)); // NaN
console.log(null == undefined); // true 规范规定
console.log(Number([])); // 0
console.log(Number(['']));// 0
console.log(Number({})); // NaN
console.log(Number(["123"])); // 123
console.log(Number(["123", "abc"])); // NaN
console.log([] == []); // false
console.log({} == {}); // false
console.log(false == "false"); // false
console.log(false == "0"); // true
值类型和引用类型
值类型
let a = 100;
let b = a;
console.log(b)// 100
引用类型
常见的引用类型
let a = { b: 100 };
let c = a;
a.b = 200;
console.log(c.b);// 200
总结
- 值类型 存储到栈中
- 引用类型,(由于json或object存储到栈中会 太大,复制太慢,所以)通过一个堆地址,存储在堆中。需要的时候通过地址从堆查找(所有的计算机程序设计都是这个思想)

常见的值类型
let a;// undefined
const b = 10;// number
const c = 'string';
const d = Symbol('sym');// symbol
const e = true;// boolean
常见的引用类型
const arr = [1, 2, 3];// Array
const obj = { a: 1 };// object
const n = null // 特殊的引用类型,指针指向空地址
// 特殊引用类型,但不用于存储数据,所以没有“拷贝 复制函数”的说法
function fn() {} // 函数类型 可执行
思考
深拷贝
const arr = [1, [2,88,77], 5, 7, 23]
var arrC = JSON.parse(JSON.stringify(arr))
arrC[1][2] = 99
console.log(arr[1])//[2,88,77]
浅拷贝
- concat()
- Object.assigin() 这种方法只能拷贝一层,有嵌套的情况就不适用了。
let a = [{x: 1}, 1,2]
let b = a.concat()
a[0] === b[0] // true 浅拷贝
如何存储??

深拷贝参考效果 let b = _.deepClone(a) : https://jsrun.net/X9gKp/edit
typeof
- 识别所有值类型
- typeof null === 'object'
- typeof undefined === 'undefined'
typeof ${value} !== 'object'可以判断是除null和undefined的基本类型- obj == null: 判断obj是null或undefined
- 识别函数
typeof function () {} === 'function' - 判断是否是引用类型(但是不可以细分)
// 1. 基本类型,直接返回
// 2. 对象或数组:迭代属性,对值递归调用深拷贝
function deepClone(obj) {
if (typeof obj !== "object" || obj == null) {
return obj;
}
let result;
if (obj instanceof Object) {
result = {};
} else {
result = [];
}
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
result[key] = deepClone(obj[key]);
}
}
return result;
}
const person = {
name: "zhangsan",
age: 10,
address: {
city: "shanghai",
house: {
number: 1
}
},
arr: ["a", 1, "b"]
};
const Lisi = deepClone(person);
person.name = "lisi";
person.address.city = "guangzhou";
console.log(Lisi.name);
console.log(Lisi.address.city);
instanceof
- 检测
prototype属性是否出现在某个实例对象的原型链上
function Car(make, model, year) {
this.make = make;
this.model = model;
this.year = year;
}
let instanseCar = new Car('make', 'model', 1924);
console.log(instanseCar instanceof Car); // true
console.log(instanseCar instanceof Object); // true
深拷贝的对象
- 值类型
- 引用类型
let objA = {
age: 10,
name: 'zhangsan',
address: {
city: 'shanghai'
},
arr: [1, 'str', 2]
}
TODO:手写深拷贝
思路:
- 值类型或null,直接返回
- 判断是对象或数组
- 遍历对象或数组,注意是自己的属性,而不是原型链的属性,递归
变量计算
字符串拼接
const a = 100 + 10 // 110
const b = 100 + '10' // '10010'
const c = true + '10' // 'true10'
const d = 100 + parseInt('10') // 110
== 运算符转换
100 == '100' // true
0 == '' // true
false == '' // true
0 == false // true
null == undefined // true
// 技巧:除了 == null 之外,其他一律用 ===
// eg:
const obj = { x: 100 }
console.log(obj.a == null) // true,相当于 obj.a === null || obj.a === undefined
if语句和逻辑计算(&& || ! 与或非)
if语句判断的就是truly和falsely变量,而不是true和false
- truly变量: !!a === true
- falsely变量: !!b === false
// 以下是falsely变量,剩下的都是truly变量
!!undefined === false
!!0 === false
!!'' === false
!!false === false
!!NaN === false
!!null === false
原型和原型链
问题
- 如何准确判断一个变量是不是数组: a instanceof Array
- class的原型的本质,怎么理解?
- 手写一个简易的jQuery,考虑插件和扩展性: 学习Class和原型的好方法
class jQuery {
constructor(selector) {
const result = document.querySelectorAll(selector);
const length = result.length;
for (let i = 0; i < length; i++) {
this[i] = result[i];
}
this.length = length;
this.selector = selector;
}
get(index) {
return this[index];
}
each(fn) {
for (let i = 0; i < this.length; i++) {
const elem = this[i];
fn(elem);
}
}
on(type, fn) {
return this.each((elem) => {
elem.addEventListener(type, fn, false);
});
}
// 扩展很多 DOM API
}
// 插件
jQuery.prototype.dialog = function (info) {
alert(info);
};
// “造轮子”
class myJQuery extends jQuery {
constructor(selector) {
super(selector);
}
// 扩展自己的方法
addClass(className) {}
style(data) {}
}
// const $p = new jQuery('p')
// $p.get(1)
// $p.each((elem) => console.log(elem.nodeName))
// $p.on('click', () => alert('clicked'))
ES6中的class
继承
- extends
- super
- 扩展或重写方法

隐式原型和显示原型
通过例子来理解
// 父类
class People {
constructor(name) {
this.name = name;
}
eat() {
console.log(this.name + " eat something");
}
}
// 子类
class Student extends People {
constructor(name) {
super(name);
}
sayHi() {
console.log(this.name + " say hi!!!");
}
}
class Teacher extends People {
constructor(name) {
super(name);
}
}
let xiaoming = new Student("xiaoming");
console.log(xiaoming instanceof Student); // true
// ES6 class的实质是function,可见语法糖
console.log(typeof Student); // "function"
console.log(typeof People); // "function"
// 原型链:实例的隐式原型指向对应的class的prototype
console.log(xiaoming.__proto__ === Student.prototype); // true
console.log(Student.prototype.__proto__ === People.prototype); // true
console.log(JSON.stringify(People.prototype));
console.log(xiaoming.sayHi());
console.log(xiaoming.eat());
原型关系
- 每个class都有一个显示原型prototype
- 每个实例都有隐式原型proto
- 实例的proto指向对应class的prototype

基于原型执行规则,获取属性或执行方法时
- 先从自身的属性或方法寻找
- 如果找不到,则去proto中查找。
- 如果还找不到,就顺着原型链继续寻找,直到Object.prototype.proto为null
eg: 比如上图中,xialuo.sayHi()执行时,先在xialuo实例自身中寻找,没有找到,就回去xialuo.proto中寻找

理解画出原型链图

作用域、闭包、自由变量/变量环境、this、执行上下文、词法分析、词法作用域、作用域链
问题
- 什么是
作用域?什么是自由变量? - 什么是
闭包?闭包的应用场景,作用? this有哪几种赋值情况?应用场景,如何取值?- 手写
bind函数
作用域
- 全局作用域
- 函数作用域
- 块级作用域(ES6新增): let, const 定义的变量有块级作用域,{}内部的区域可以使用
if (true) {
let a = 1;
}
console.log(a);// 报错,在块级作用域外无法读取a的值,a is not defined
自由变量
在A作用域中使用的变量x,却没有在A作用域中声明(即在其他作用域中声明的),对于A作用域来说,x就是一个自由变量。
- 一个变量在当前作用域没有定义,但是被使用了
- 向上级作用域一层一层的找,直到找到为止
- 如果到全局作用域都没有找到,就会报错: x is not defined
下方闭包的函数作为参数会体现: 所有自由变量的查找,是在函数定义的地方,向上级作用域查找,而不是在执行的地方
闭包
闭包是作用域的特殊情况
函数作为返回值
function create() {
let a = 100;
return function() {
console.log(a)
}
}
let fn = create()
let a = 200;
fn();// 结果是多少?
// 100函数作为参数
function create(fn) {
let a = 200;
fn();
}
let a = 100;
let fn = function() {
console.log(a)
}
create(fn);
// 结果是100:所有自由变量的查找,是在函数定义的地方,向上级作用域查找,而不是在执行的地方function bar() {
console.log(myName);
}
function foo() {
var myName = "极客邦";
bar();
}
var myName = "极客时间";
foo();
// 极客时间
// 题目一
let myname = '诸葛亮'
{
console.log(myname)
let myname = '刘备'
}
// 题目二
let myname = '诸葛亮'
{
console.log(myname)
}
答案:
- 题目一
- Uncaught ReferenceError: Cannot access 'myname' before initialization
- 题目二
- 诸葛亮
【分析】
- 题一:let 在块级作用域中定义了一个变量,在预解析的时候,注意到声明的变量,所以不会去外部作用域查找变量。
会存在暂时性死区,在这个区域中读取会爆错:
Cannot access 'myname' before initialization - 题二:自由变量,在块级作用域内找不到,会沿着作用域链往上找
【总结】
- 变量流程:创建、初始化、赋值
- 预解析:JavaScript 引擎在执行任何代码片段(例如函数调用)之前,
会先对其中
所有变量(包括函数)声明进行处理,这是一个预解析的过程。 - var 的创建和初始化会提升,但是赋值不会提升
let和const在块级作用域中定义了一个变量,在预解析的时候,注意到声明的变量,所以不会去外部作用域查找变量- function的创建、初始化和赋值均会被提升。
【参考】
核心概念
执行上下文
什么是执行上下文

执行上下文包括变量环境、词法环境、外部环境、this
- 全局执行上下文
- 函数执行上下文
调用栈(Call Stack)
将管理执行上下文的栈称为执行上下文栈,也称调用栈
词法作用域
词法作用域指作用域是由代码中函数声明的位置(词法分析是静态分析,位置决定)来决定的,所以词法作用域是静态的作用域,
通过它就能够预测代码再执行过程中如何查找标识符。
词法分析也是通过代码结构静态分析
词法环境 和 变量环境
作用域链
JavaScript 的作用域链是由词法作用域决定的,而词法作用域是由代码结构来决定的,因为词法分析也是通过代码结构静态分析的。
块级作用域
单个执行上下文中如何查找变量
JS 作用域
- 全局作用域
- 函数作用域
- 块级作用域(ES6 后支持,{} 里形成)
- 创建上下文
- 函数内部通过 var 声明的变量,在编译阶段会存放到
变量环境中 - let 或 const 声明的变量,存在到
词法环境中 - 在函数的作用域块内部,通过 let 或 const 声明的变量没有放到
词法环境中
- 执行代码
- 当进入
函数的作用域块时,会在词法环境中维护一个栈结构,会将let 或者 const 声明的变量压到栈顶, 跳出块级作用域后会弹出
在词法环境内部,维护了一个小型栈结构,栈底是函数最外层的变量(通过 let 或者 const 声明的变量), 进入一个作用域块后,就会把该作用域块内部的变量压到栈顶; 当作用域执行完成之后,该作用域的信息就会从栈顶弹出,这就是词法环境的结构。
【答】
- 单个执行上下文查找的过程:
- 从
词法环境中,从栈顶(块级作用域)向下查询。也就是从块级作用域 -> 块级作用域外层(函数作用域) - 找不到再去
变量环境中查找
- 从
09 | 块级作用域:var缺陷以及为什么要引入let和const?
作用域链中如何查找变量
说出下面几种情况中 this 的取值
- 作为普通函数去调用
- 使用call bind apply
- 作为对象方法被调用
- 在class方法中调用
- 箭头函数
this 取什么样的值,是在函数中执行的时候确定的,而不是在定义的时候,与自由变量刚好相反
function fn1() {
console.log(this)
}
fn1() // window
fn1.call({ x: 100})// 直接执行:{ x: 100 }
const fn2 = fn1.bind({ x: 200 })
fn2()// bind只绑定,不执行,所以需要手动执行 { x: 200 }
https://codepen.io/huangzonggui/pen/GRmawQW

作用域的相关问题
- this的不同应用场景,如何取值?
- 作为普通函数去调用: this指向window
- 使用call bind apply: 指向绑定者
- 作为对象方法被调用:指向当前对象
- 在class方法中调用:class实例本身
- 箭头函数: 指向上一作用域的值
- 箭头函数表达式的语法比函数表达式更简洁,并且没有自己的
this,arguments,super或new.target。
- 箭头函数表达式的语法比函数表达式更简洁,并且没有自己的
- 手写bind函数
- 实际开发中闭包的应用场景,举例说明
var bar = {
myName:"time.geekbang.com",
printName: function () {
console.log('this: ', this)
console.log(myName)
console.log(this.myName)
}
}
function foo() {
let myName = "极客时间"
return bar.printName
}
let myName = "极客邦"
let _printName = foo()
_printName()
bar.printName()

11 | this:从JavaScript执行上下文的视角讲清楚this
原型链中的this
class People {
constructor(name) {
this.name = name;
}
}
class Student extends People {
constructor(name, number) {
super(name);
this.number = number;
}
sayHi() {
console.log(`姓名 ${this.name} 學號 ${this.number}`);
}
}
const stu1 = new Student("xialuo", "123");
stu1.sayHi();// "姓名 xialuo 學號 123"
stu1.__proto__.sayHi();// "姓名 undefined 學號 undefined"
// obj.__proto__.fun() 的时候this指向了obj.__proto__,这个对象没有name和number
// 实际obj.fun() 执行原理类似obj.__proto__.fun.call(obj) this指向obj
// console.log(stu1);
// console.log(stu1.__proto__);
在JavaScript中,函数的this关键字的值是根据函数的调用方式确定的。具体来说,它取决于函数被调用时的执行上下文。
// 1
var myObj = {
name : "极客时间",
showThis: function(){
this.name = "极客邦"
console.log(this)
}
}
var foo = myObj.showThis
foo()
// this 指向window,在()执行的时候是在全局作用域下
// 2
var myObj = {
name : "极客时间",
showThis: function(){
this.name = "极客邦"
console.log(this)
}
}
var foo = () => {
myObj.showThis()
console.log("foo 箭头 this 是 window, 因为在执行的时候是子啊全局作用域下执行的:", this)
}
foo()
// showThis 的 this 指向是 myObj,因为执行的时候是通过 myObj 来调用的
// 3
var myObj = {
name : "极客时间",
showThis: function(){
this.name = "极客邦"
console.log(this)
}
}
var foo = function() {
myObj.showThis()
console.log("foo 箭头 this 是 window, 因为在执行的时候是子啊全局作用域下执行的:", this)
}
foo()
构造函数
function CreateObj() {
this.name = 'newObj'
}
var myObj = new CreateObj()
console.log(myObj)
实现过程
- 创建临时对象 tempObj
- 调用 Object.call 传入 tempObj 参数
- 返回 tempObj
function CreateObj() {
this.name = 'newObj'
}
var tempObj = {}
CreateObj.call(tempObj)
console.log(tempObj)
this 的设计缺陷
- 函数不会继承上一级作用域的 this
- 声明变量 that 来记录上级作用域
- 箭头函数
var myObj = {
name : "极客时间",
showThis: function(){
console.log(this)
function bar(){console.log(this)}
bar()
}
}
myObj.showThis()
// 声明变量
var myObj = {
name : "极客时间",
showThis: function(){
console.log(this)
var self = this
function bar(){
self.name = "极客邦"
}
bar()
}
}
myObj.showThis()
console.log(myObj.name)
console.log(window.name)
// 箭头函数
var myObj = {
name : "极客时间",
showThis: function(){
console.log(this)
var bar = ()=>{
this.name = "极客邦"
console.log(this)
}
bar()
}
}
myObj.showThis()
console.log(myObj.name)
console.log(window.name)
- 普通函数中的 this 会指向全局对象 window
- 通过 call 来显示调用
- 严格模式下指向 undefined
任务: 闭包的应用场景
1.防抖
// 防抖:连续触发多次,只执行最后一次; 應用場景:input裡連續輸入,button多次點擊
// 节流:连续触发多次,一定时间内只触发一次;應用場景:鼠標移動,讀取鼠標的位置
const fn = () => console.log("fn");
window.onresize = debounce(fn, 500);
function debounce(fn) {
let timer;
return function () {
if (timer) {//timer第一次执行后会保存在内存里 永远都是执行器 直到最后被触发
clearTimeout(timer);
}
timer = setTimeout(() => fn(), 500);
};
}
2.使用闭包设计单例模式
// 单例模式:创建多个实例的时候,只能创建单个实例
class createUser {
constructor(name) {
this.name = name;
}
}
// let a = new createUser("aa");
// console.log(a.name);
// 代理创建单例模式
const ProxyFun = (() => {
let instance = null;
// 通過閉包 訪問閉包通過返回函數來訪問內部變量
return function (name) {
if (!instance) {
instance = new createUser(name);
}
return instance;
};
})(); // 立刻執行,返回一個function
const b = ProxyFun("b");
const c = ProxyFun("c");
console.log(b.name, c.name);
console.log(b === c);
异步
问题一: 单线程和异步(同步和异步的区别)?
- js是单线程语言,只能同时做一件事
- 浏览器和nodejs已支持JS启动
进程如Web Worker - JS和DOM渲染共同一个线程,因为JS可修改DOM结构
- 遇到等待(网络请求,定时任务)不可以卡住
- 需要异步解决卡住的问题
- alert是同步,会阻塞代码执行
- setTimeout是异步,不阻塞执行
- 回调callback函数形式
问题二:手写用Promise加载一张图片
// 加载图片Fun 成功后和失败后的处理
const loadImage = (url) => {
return new Promise((resolve, reject) => {
let img = document.createElement("img");
img.onload = () => {
resolve(img);
document.body.appendChild(img);
};
img.onerror = () => {
reject(new Error("load image error"));
};
img.src = url;
});
};
const url =
"https://cdn.pixabay.com/photo/2015/04/23/22/00/tree-736885__480.jpg";
loadImage(url)
.then((data) => {
console.log('width: ', data.width);
return data;
})
.then((data) => {
console.log('height: ', data.height);
})
.catch((error) => console.error(error));
- 问题三:前端使用异步的应用场景
- 网络请求 如ajax 图片加载
- 定时任务 setTimeout、setInteral
相关知识
- 单线程和异步
- 应用场景
- callback hell 和 Promise
event loop
要会画出图,并讲述过程
图组成部分
- Browser console
- Call Stack
- Web APIs(类似setTimeout的时候,存放timer用的)
- Callback Queue(Event Loop)
event Loop(事件轮询/事件循环)流程
- 同步代码,一行一行放在Call Stack执行
- 遇到异步代码,先记录下来,等待时机(定时、网络请求等)
- 时机到了,将异步代码移动到Callback Queue中
- 如Call Stack 为空的时候,Event Loop开始工作
- 轮询查找Callback Queue,如有则移动到Call Stack执行
- 继续轮询查找(永动机一样)
DOM事件和event loop 的关系
$('#btn1').click(function (e) {// 1. 执行到这一行的时候,将callback function暂存到Web APIs里
console.log('click event'); // 2. 当点击的时候,立刻将callback function 提交到Callback Queue中,event loop轮询的时候,将callback function移到call Stack中执行
})
- 异步(setTimeout, ajax等)使用回调,基于event loop
- DOM事件(不是异步)使用回调,基于event loop,
DOM渲染
- 当Call Stack空闲的时候,会尝试渲染DOM,再出发Event Loop、
- JS是单线程的,而且和DOM渲染共用一个线程

例子:https://coding.imooc.com/lesson/400.html#mid=35171
console.log('Hi');
setTimeout(() => {
console.log('callback1');
}, 5000);
console.log('Bye');

Promise
主要是为了解决回调地狱嵌套(callback hell)的问题
ES6解决异步问题常用方法(语法糖):async await
- 三种状态:Pending、fulfilled(resolved)、rejected
- then正常返回resolved,里面报错则返回rejected
catch正常返回resolved,里面报错则返回rejected- fulfilled(resolved) 触发 then 回调,reject 触发 catch 回调
const p1 = Promise.reject("reject")
.then((data) => {
console.log("then 1:", data);
})
.catch((error) => {
console.log("error 1:", error);
})
.then((data) => {
console.log("then 2:", data);
})
.catch((error) => {
console.log("error 1:", error);
});
console.log('p1: ', p1);// fulfilled
// error 1: reject
// then 2
//
const p2 = Promise.reject('error').catch(err => {
console.error(err)
})
console.log('p2:', p2)// fulfilled
const p3 = Promise.reject('error').catch(err => {
throw new Error('error on catch')
})
console.log('p3:', p3)// rejected
题目
// 题目
// Promise.resolve().then(() => {
// console.log(1)
// }).catch(() => {
// console.log(2)
// }).then(() => {
// console.log(3)
// })
// 1 3
//题目
// Promise.resolve().then(() => {
// console.log(1)
// throw new Error('error')
// }).catch(() => {
// console.log(2)
// }).then(() => {
// console.log(3)
// })
// // 1 2 3
async/await 和 Promise 的关系
- 执行async函数,返回的是Promise对象
- await相当于Promise的then
!(async function() {
const prom = Promise.reject('1')
const res = await prom// 这一行不执行,因为await相当于then,reject的时候不走then
console.log('res', res)// 这一行不会执行,因为在上一行已经报错
})()
async await
- 同步的语法,实现异步
- await需要async包裹
- await后可以是promise、async function
async function async1() {
console.log("async1 start");// 这一行还没有到异步
await async2();
// await后面的都可以看做是callback里面的内容,异步
console.log("async1 end");
}
async function async2() {
console.log("async2");
}
console.log("script start");
setTimeout(() => {
console.log("setTimeout");
}, 0);
async1();
new Promise((resolve) => {
console.log("promise resolve");
resolve();
}).then(() => {
console.log("promise then");
});
console.log("script end");
// script start
// async1 start
// async2
// promise resolve
// script end
// async1 end
// promise then
// setTimeout
for-of的应用场景
function muti(num) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(num * num);
}, 1000);
});
}
const nums = [1, 2, 3];
// nums.forEach(async (i) => {
// const res = await muti(i);
// console.log(res);// 一下子打印 并发执行
// });
// 官网解析:forEach() expects a synchronous function — it does not wait for promises.
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach#description
// (async function () {
// for (let i = 0; i < nums.length; i++) {
// const res = await muti(nums[i]);
// console.log(res);// 逐个打印出来
// }
// })();
!(async function () {
for (let j of nums) {
const res = await muti(j);
console.log(res); // 异步打印 阻塞打印,等待一秒后打印1,1s后打印4,1s后打印9
// 有结果后再执行下一个
}
})();
for 循环:一般用于循环固定次数的操作,例如遍历数组、字符串等。如果需要遍历对象,需要使用 for...in 或者 Object.keys() 方法。
for...in 循环:一般用于遍历对象的属性,包括原型链上的属性。需要注意的是,使用 for...in 循环遍历对象时,属性的遍历
顺序是不确定的。也可以遍历数组。
// 深拷贝
const deepClone = (obj) => {
if (typeof obj !== 'object' || obj === null) {
return obj
}
let result = Array.isArray(obj) ? [] : {}
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
if (typeof obj[key] === 'object' && obj[key] !== null) {
result[key] = deepClone(obj[key])// 引用类型,递归赋值
} else {
result[key] = obj[key]// 值类型直接赋值
}
}
}
return result
}
forEach 循环:一般用于遍历数组,可以使用回调函数对数组中的每个元素进行操作。但是不能使用 await 关键字等待异步操作的完成,需要使用其他方式处理异步操作。
for...of 循环:一般用于遍历可迭代对象,例如数组、字符串、Set 等。可以使用
await 关键字等待异步操作的完成。与 forEach 循环不同的是,for...of 循环是同步的,会等待上一个元素的操作完成后再执行下一个元素。
微任务microTask 宏任务macroTask
- 宏任务
- setTimeout
- setInterval
- Ajax
- DOM事件
- 微任务
- Promise
- async await
- 微任务比宏任务执行的要早
- 微任务在DOM渲染前触发
- 宏任务在DOM渲染后触发
微任务会在执行任何其他事件处理,或渲染,或执行任何其他宏任务之前完成。 这很重要,因为它确保了微任务之间的应用程序环境基本相同(没有鼠标坐标更改,没有新的网络数据等)。
// 1.
// console.log("length:", document.getElementById("container").children.length);
// alert("本次Call stack 結束,DOM结构已更新,但尚未触发渲染");
// 2. 微任务在DOM渲染之前
Promise.resolve().then(() => {
console.log("length:", document.getElementById("container").children.length);
alert("promise then"); // 未渲染,页面看不到
});
//setTimeout(() => {
//console.log("length:", document.getElementById("container").children.length); // 3
//alert("setTimout "); // 已渲染
//});


为什么微任务比宏任务执行的要早:
- 微任务是ES6规定的
- 宏任务是浏览器规定的
- 执行Event Loop的时候,执行到Promise的微任务的时候,先放到micro task queue里面,与Callback Queue分开(宏任务)


console.log(100);
// 宏任务
setTimeout(() => console.log(200), 0);
// 微任务
Promise.resolve().then(() => console.log(300));
console.log(400);
// 100 400 300 200
总结:
- 执行调用栈中 JS
- 执行最早的一个宏任务
- 执行所有微任务
- 尝试渲染
- 转到步骤1
JS-Web-API
DOM
vue和react封装了DOM的操作
带着问题学习DOM
- DOM是哪种数据结构
- DOM操作的常用API
- attr和property的区别
- 如何一次性插入多个DOM节点,考虑性能
知识点
DOM的本质
- 树
document.createElement(name)document.getElementById(id)document.getElementsByTagName(name)document.getElementsByClassName(className)document.getElementsByTagName(tagName)document.querySelectorAll:返回一个 NodeList ,IE8+(含)。document.forms:获取当前页面所有form,返回一个 HTMLCollection ;
DOM结构操作
新建节点
- createElement('p')
插入节点
- appendChild(${新键节点})
移动节点
- 对现有的节点进行appendChild(${现有节点})
获取子元素节点
每个node都包含有nodeType属性。
let NodeListRes = document.getElementById("div1").childNodes;
console.log(NodeListRes); // 有七个
// nodeType取值:
// 元素节点:1 属性节点:2 文本节点:3 注释节点:8
NodeListRes = Array.prototype.slice
.call(NodeListRes)
.filter((item) => item.nodeType === 1);
console.log("filter result:", NodeListRes);
获取父元素节点
document.getElementById("p2").parentNode;删除节点
document.getElementById("div1").removeChild(document.getElementById("p3"));
DOM性能
DOM操作是比较耗费性能的
避免频繁查询,查询做缓存
// 频繁查询DOM
// for (let i = 0; i < document.getElementsByTagName("p").length; i++) {
// console.log(i);
// }
// 做缓存 一次查询
let tagLength = document.getElementsByTagName("p").length;
for (let i = 0; i < tagLength; i++) {
console.log(i);
}避免频繁操作
attr 和 property 的区别
<div id='div1' class='container'>
<p>this is a p tag</p>
<p>this is a p tag</p>
<p>this is a p tag</p>
</div>
let pList = document.querySelectorAll("p");
// set property
pList[0].a = "a";
// set attribute
pList[1].setAttribute("a", "a");
attribute修改HTML属性,会反映到HTML结构上- property修改自定义的JS属性,不会反映到HTML结构上
- 需要注意一点,style 是一个比较特殊的例子,修改它的属性,会触发 DOM 样式的改动,会体现到 DOM 中。而我们日常所说的 property,更多是
自定义的属性,例如 p1.a = 100; p1.b = 200 这样子。
- 需要注意一点,style 是一个比较特殊的例子,修改它的属性,会触发 DOM 样式的改动,会体现到 DOM 中。而我们日常所说的 property,更多是
- 修改attribute会导致DOM渲染,修改property有可能导致DOM渲染,但是建议尽量使用property,DOM渲染会耗费性能
注意区分:prop 和 property 的区别
- prop 是 vue 的语法糖,实际是对 attribute 的封装,
- property 是 js 的对象属性
BOM (Browser Object Model)
题目
- 如何识别浏览器
- 分拆url的各个部分
知识点
- navigator
- screen
- location
- history
识别浏览器
一般用navigator.userAgent来判断浏览器,但是,这个判断是不严谨的,就好像看到一个鸭子,走路和叫声像鸭子,就判定是鸭子。应该使用各个浏览器的特征来判断
另外,浏览器为了网页能在自己网页中运行,userAgent加了很多标识。
例如,Chrome的userAgent
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36

// Opera 8.0+
var isOpera = (!!window.opr && !!opr.addons) || !!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0;
// Firefox 1.0+
var isFirefox = typeof InstallTrigger !== 'undefined';
// Safari 3.0+ "[object HTMLElementConstructor]"
var isSafari = /constructor/i.test(window.HTMLElement) || (function (p) { return p.toString() === "[object SafariRemoteNotification]"; })(!window['safari'] || (typeof safari !== 'undefined' && safari.pushNotification));
// Internet Explorer 6-11
var isIE = /*@cc_on!@*/false || !!document.documentMode;
// Edge 20+
var isEdge = !isIE && !!window.StyleMedia;
// Chrome 1 - 71
var isChrome = !!window.chrome && (!!window.chrome.webstore || !!window.chrome.runtime);
// Blink engine detection
var isBlink = (isChrome || isOpera) && !!window.CSS;
var output = 'Detecting browsers by ducktyping:<hr>';
output += 'isFirefox: ' + isFirefox + '<br>';
output += 'isChrome: ' + isChrome + '<br>';
output += 'isSafari: ' + isSafari + '<br>';
output += 'isOpera: ' + isOpera + '<br>';
output += 'isIE: ' + isIE + '<br>';
output += 'isEdge: ' + isEdge + '<br>';
output += 'isBlink: ' + isBlink + '<br>';
document.body.innerHTML = output;
参考
- https://jsfiddle.net/6spj1059/
- https://stackoverflow.com/questions/9847580/how-to-detect-safari-chrome-ie-firefox-and-opera-browser
Screen(window.screen)
- screen.width
- screen.height
location
console.log('href:', location.href);
console.log('protocol:', location.protocol);
console.log('host:', location.host);
console.log('hostname:', location.hostname);
console.log('hast:', location.hash);
console.log('port:', location.port);
console.log('search:', location.search);
history
- history.back()
- history.forwork()
- history.go(1)
事件
题目
- 编写一个通用的事件绑定函数
- 描述冒泡的过程
- 无线下拉的图片列表,如何监听每个图片点击
知识点
- 事件绑定
- 事件冒泡
- 事件代理
事件绑定
let btnElement = document.getElementById("btn");
// 普通的事件绑定
// btnElement.addEventListener("click", () => alert("alert"));
// 通用的事件绑定(未完善,见下方兼容事件代理的 事件绑定函数)
const myBind = function (type, callback, target) {
target.addEventListener(type, callback);
};
myBind("click", () => alert("myBind"), btnElement);
改造事件绑定
<div id='div1' class='container'>
<p id="p1">点击我 阻止冒泡 </p>
<p>this is a p tag</p>
<p>this is a p tag</p>
<p>this is a p tag</p>
</div>
<div id='div2' class='container'>
<a href="#">a1</a>
<a href="#">a2</a>
<a href="#">a3</a>
<a href="#">a4</a>
<button>i am button</button>
</div>
// 冒泡
// 通用的事件绑定:考虑事件代理
const bindEvent = function (elem, eventType, selector, callback) {
if (callback == null) {
callback = selector;
selector = null;
}
elem.addEventListener(eventType, (event) => {
console.log('event :>> ', event);
if (selector) {
// 事件代理
if (event.target.matches(selector)) {
callback.call(event.target, event)// 调用的时候,this可以指向callback里的function
// callback()
}
} else {
// 普通绑定
callback.call(event.target, event)
// callback()
}
});
};
const bodyElem = document.body;
// const bodyElem = document.getElementById("div1");
bindEvent(bodyElem, "click", function (event) {
alert(this.innerHTML);
});
// 普通绑定
const p1Elem = document.getElementById("p1");
bindEvent(p1Elem, "click", function (event) {
event.stopPropagation();// 阻止冒泡
alert(this.innerHTML);
});
// 事件代理:基于冒泡事件实现
// 子元素太多,逐个绑定事件会很复杂,所以通过事件代理到父元素上
const div2 = document.getElementById('div2');
// 未传selector的时候,自己处理判断是否是触发的目标元素
// bindEvent(div2, 'click', function (event) {
// event.stopPropagation();
// event.preventDefault();// 阻止a标签的跳转: #
// if (event.target.nodeName === 'A') {
// console.log('是a標籤')
// alert(this.innerHTML)
// }
// })
// 传selector, 改造 事件绑定,兼容事件代理
bindEvent(div2, 'click', 'A', function (event) {
event.stopPropagation();
event.preventDefault();// 阻止a标签的跳转: #
// if (event.target.nodeName === 'A') {
// console.log('是a標籤')
alert(this.innerHTML)
// }
})
答:描述事件冒泡的过程
- 基于DOM树形结构
- 事件顺着触发元素向上冒泡
- 应用:事件代理
答:无线下拉的图片列表,如何监听每个图片点击
- 事件代理
- 用event.target来获取触发的元素
- 通过matches来判断 触发元素 是否是图片
AJAX(Asynchronous JavaScript And XML)
AJAX(Asynchronous JavaScript And XML )是一种使用 XMLHttpRequest 技术构建更复杂,动态的网页的编程实践。
目标
手写一个简易的AJAX(用Promise)
不用具体考虑所有的方法和状态码(重复造轮子)
function myAjax(url) {
return new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest(url);
xhr.open("GET", url, true);
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.code === 200) {
resolve(JSON.parse(xhr.responseText));
} else if (xhr.code === 404) {
reject(new Error("error"));
}
}
};
xhr.send(null);
});
}
let res = myAjax("./myAjax.json");
res.then((data) => {
console.log("data:", data);
});
跨域常用的实现方式
知识点
- XMLHttpRequest
XMLHttpRequest(XHR) 是一种创建 AJAX 请求的 JavaScript API 。它的方法提供了在浏览器和服务器之间发送请求的能力。
- 状态码 XMLHttpRequest.status
status码是标准的HTTP status codes XMLHttpRequest.onreadystatechange
XMLHttpRequest.readyState: 4的時候,才可以获取到response
| 分类 | 分类描述 |
|---|---|
| 0 | (未初始化) |
| 1 | (载入) |
| 2 | (载入完成) |
| 3 | (交互) |
| 4 | (完成)响应内容解析完成 |

跨域:同源策略,跨域解决方案
浏览器的同源策略
- AJAX请求时,浏览器要求当前页面和server必须同源(安全)
- 同源:协议、域名、端口,三者必须一致。一致代表同一来源
- 前端:浏览器浏览 http://a.com:8080/ api只能是:http://a.com:8080/api/xxx 如果server不是,则会被浏览器拦截
- 但是服务器访问服务器的数据,是不会拦截的,因为没有经过浏览器,所以出现服务器向服务器发起攻击的安全事件
加载图片、css、js不受浏览器的同源策略限制
<img src=跨域的图片地址 />:通过图片实现统计打点,使用第三方统计服务<link href=跨域的css地址 />:CDN<script src=跨域的js地址></script>:CDN 实现JSONP
跨域(CORS: Cross-Origin requests 跨域资源共享 )
- 所有的跨域,都必须经过server端允许和配合
- server配置: Access-Control-Allow-Origin(访问控制允许来源)、
- 所有的跨域,都必须经过server端允许和配合
解决跨域方案
纯服务器
服务器 配置http header: Access-Control-Allow-Origin
```
// 第二个参数填写允许跨域的名称,不建议*
response.serHeader("Access-Control-Allow-Origin", "http://localhost:8011");
response.serHeader("Access-Control-Allow-Headers", "X-Requested-With");
response.serHeader("Access-Control-Allow-Origin", "PUT,POST,GET,DELETE,OPTIONS");
response.serHeader("Access-Control-Allow-Credentials", "true");
```
需要服务端配合
JSONP:JSON with Padding (填充式 JSON) (不推荐使用)
实现方式
通过script获取数据,返回
```
// 后台处理返回
callback({
name: 'zhangsan',
age: 12
})
```前端处理script(跨域的URL)请求数据
```
window.callback = function(data) {
console.log(data)// 成功取到跨域的数据
// {
// name: 'zhangsan',
// age: 12
// }
}
```

缺点
- 只支持GET
- 安全性不够
- JSONP暴露了很多漏洞,它假定了传送回来的代码是可信的,进一步暴露了CSRF(跨站点请求伪造)漏洞。
- 没有错误处理,调试困难
纯前端
- 代理
思考🤔
- 是不是可以通过跨域的方法,谁都可以做一个山寨淘宝出来?如果是这样,那么跨域好像就没有什么意义了吧。虽然有同源策略限制,但是大家都知道解决这个限制的方法,那就相当于没有限制?
AJAX的一些插件
- jQuery
- Fetch
- 注意: 当接收到一个代表错误的 HTTP 状态码时,从
fetch()返回的 Promise 不会被标记为 reject, 即使响应的 HTTP 状态码是 404 或 500。相反,它会将 Promise 状态标记为 resolve (但是会将 resolve 的返回值的ok属性设置为 false ),仅当网络故障时或请求被阻止时,才会标记为 reject。
- 注意: 当接收到一个代表错误的 HTTP 状态码时,从
- axios
存储
Cookie
特点
- 存储大小,最大4k
- 随着http请求,
用途
- 会话状态管理(如用户登录信息、购物车、游戏分数或其他需要记录的信息)
- 个性化设置(如用户的个性化设置,主题等)
- 浏览器行为跟踪(如跟踪分析用户行为等)
观点
- 现在随着现代浏览器开始支持各种各样的存储方式,Cookie 渐渐被淘汰。由于服务器指定 Cookie 后,浏览器的每次请求都会携带 Cookie 数据,会带来额外的性能开销(尤其是在移动环境下)。新的浏览器API已经允许开发者直接将数据存储到本地,如使用 Web storage API (本地存储和会话存储)或 IndexedDB 。
安全
JavaScript 通过 Document.cookie 访问 Cookie
JavaScript可以通过跨站脚本(xss)来窃取cookie,
[维基百科](https://en.wikipedia.org/wiki/HTTP_cookie#Cross-site_request_forgery)已经给了一个比较好的 [CSRF](https://developer.mozilla.org/zh-CN/docs/Glossary/CSRF) 例子。比如在不安全聊天室或论坛上的一张图片,它实际上是一个给你银行服务器发送提现的请求:
```
<img src="http://bank.example.com/withdraw?account=bob&amount=1000000&for=mallory">
```当你打开含有了这张图片的 HTML 页面时,如果你之前已经登录了你的银行帐号并且 Cookie 仍然有效(还没有其它验证步骤),你银行里的钱很可能会被自动转走。有一些方法可以阻止此类事件的发生:
- 对用户输入进行过滤来阻止 XSS (en-US);
- 任何敏感操作都需要确认;
- 用于敏感信息的 Cookie 只能拥有较短的生命周期;
- 更多方法可以查看OWASP CSRF prevention cheat sheet。
localStorage和sessionStorage
- 特点
- HTML5专门为存储而设计,大小限制为5M
- API易用:getItem('key', 'value')、setItem('key')、removeItem('key')、clear()
- 不会随着http请求发送出去
- 区别
- localStorage会持久化存储,除非代码删除或手动删除
- sessionStorage 关闭对应浏览器窗口(Window)/ tab,会清除对应的
sessionStorage。
TODO: 拓展思考🤔
- 不用cookie的话,用什么方式实现登录信息的保存?这个方式如何避免cookie的安全问题?
- token登录的过程是如何实现的?
- 如何持久化保存用户的登录信息?
常见正则
掌握基本的场景正则,手写
- 用户名(判断字符串已字母开头,后面字母数字下划线,长度6-30)
- 邮件
- 小写英文字母
- 英文字母
- 邮政编码
- 简单的ip地址
- 日期格式(2021.09.11)
正则学习网站:https://deerchao.cn/tutorials/regex/regex.htm
思考🤔(题目)
Object.create() 和 new Object()的区别
new Object 和 {} 新键对象相同,原型都是Object.prototype
Object.create()使用现有的对象来提供新键对象的proto
let obj = {
a: 100
};
let objectCreateObj = Object.create(null);
console.log(objectCreateObj.__proto__); // undefined
let objectCreateWithObj = Object.create(obj);
console.log(objectCreateWithObj.a); // 100
console.log(objectCreateWithObj.__proto__ === obj); //true
JS如何实现继承
- class继承(ES6引入class)
class Animal {
constructor(name) {
this.name = name;
}
}
class Cat extends Animal {
constructor(name) {
super(name);
}
say () {
return 'Hello, ' + this.name + '!'
}
}
// 测试:
var kitty = new Cat('Kitty');
var doraemon = new Cat('哆啦A梦');
if ((new Cat('x') instanceof Animal)
&& kitty
&& kitty.name === 'Kitty'
&& kitty.say
&& typeof kitty.say === 'function'
&& kitty.say() === 'Hello, Kitty!'
&& kitty.say === doraemon.say)
{
console.log('测试通过!');
} else {
console.log('测试失败!');
}
参考 https://www.liaoxuefeng.com/wiki/1022910821149312/1072866346339712
- prototype继承
function Person(first, last, age, gender) {
this.name = {
first,
last
};
this.age = age;
this.gender = gender;
}
function Teacher(first, last, age, gender, subject) {
Person.call(this, first, last, age, gender);
this.subject = subject;
}
let teacher1 = new Teacher("aki", "huang", 20, "男", "王牌讲师");
console.log(teacher1);
https://codepen.io/huangzonggui/pen/abwWgdN?editors=0011
参考资料 https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/Objects/Inheritance
参考