跳到主要内容

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和原型的好方法
    • 实现基本的api:get each on
      • 先熟悉jQuery这些方法: eachgeton
      • 实现简易的jQuery
      • 考虑插件
      • 考虑扩展性:集成
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
  • 扩展或重写方法

隐式原型和显示原型

通过例子来理解

codepen 代码

// 父类
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就是一个自由变量。

  1. 一个变量在当前作用域没有定义,但是被使用了
  2. 向上级作用域一层一层的找,直到找到为止
  3. 如果到全局作用域都没有找到,就会报错: 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 的创建和初始化会提升,但是赋值不会提升
  • letconst 在块级作用域中定义了一个变量,在预解析的时候,注意到声明的变量,所以不会去外部作用域查找变量
  • function的创建、初始化和赋值均会被提升。

【参考】

核心概念

执行上下文

什么是执行上下文

执行上下文包括变量环境、词法环境、外部环境、this

  • 全局执行上下文
  • 函数执行上下文

调用栈(Call Stack)

将管理执行上下文的栈称为执行上下文栈,也称调用栈

词法作用域

词法作用域指作用域是由代码中函数声明的位置(词法分析是静态分析,位置决定)来决定的,所以词法作用域是静态的作用域,

通过它就能够预测代码再执行过程中如何查找标识符。

词法分析也是通过代码结构静态分析

词法环境 和 变量环境

作用域链

JavaScript 的作用域链是由词法作用域决定的,而词法作用域是由代码结构来决定的,因为词法分析也是通过代码结构静态分析的。

块级作用域

单个执行上下文中如何查找变量

JS 作用域

  • 全局作用域
  • 函数作用域
  • 块级作用域(ES6 后支持,{} 里形成)
  1. 创建上下文
  • 函数内部通过 var 声明的变量,在编译阶段会存放到变量环境
  • let 或 const 声明的变量,存在到词法环境
  • 在函数的作用域块内部,通过 let 或 const 声明的变量没有放到词法环境中
  1. 执行代码
  • 当进入函数的作用域块时,会在词法环境中维护一个栈结构,会将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

image.png

作用域的相关问题

  • this的不同应用场景,如何取值?
    • 作为普通函数去调用: this指向window
    • 使用call bind apply: 指向绑定者
    • 作为对象方法被调用:指向当前对象
    • 在class方法中调用:class实例本身
    • 箭头函数: 指向上一作用域的值
      • 箭头函数表达式的语法比函数表达式更简洁,并且没有自己的thisargumentssupernew.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渲染共用一个线程

image.png

例子:https://coding.imooc.com/lesson/400.html#mid=35171

console.log('Hi');

setTimeout(() => {
console.log('callback1');
}, 5000);

console.log('Bye');

image.png

Promise

主要是为了解决回调地狱嵌套(callback hell)的问题

ES6解决异步问题常用方法(语法糖):async await

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
// 有结果后再执行下一个
}
})();

  1. for 循环:一般用于循环固定次数的操作,例如遍历数组、字符串等。如果需要遍历对象,需要使用 for...in 或者 Object.keys() 方法。

  2. 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
}
  1. forEach 循环:一般用于遍历数组,可以使用回调函数对数组中的每个元素进行操作。但是不能使用 await 关键字等待异步操作的完成,需要使用其他方式处理异步操作。

  2. for...of 循环:一般用于遍历可迭代对象,例如数组、字符串、Set 等。可以使用 await 关键字等待异步操作的完成。与 forEach 循环不同的是,for...of 循环是同步的,会等待上一个元素的操作完成后再执行下一个元素。

微任务microTask 宏任务macroTask

  • 宏任务
    • setTimeout
    • setInterval
    • Ajax
    • DOM事件
  • 微任务
    • Promise
    • async await
  • 微任务比宏任务执行的要早
    • 微任务在DOM渲染前触发
    • 宏任务在DOM渲染后触发

      微任务会在执行任何其他事件处理,或渲染,或执行任何其他宏任务之前完成。 这很重要,因为它确保了微任务之间的应用程序环境基本相同(没有鼠标坐标更改,没有新的网络数据等)。

let me try

// 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 "); // 已渲染
//});

image.png

image.png

为什么微任务比宏任务执行的要早:

  • 微任务是ES6规定的
  • 宏任务是浏览器规定的
  • 执行Event Loop的时候,执行到Promise的微任务的时候,先放到micro task queue里面,与Callback Queue分开(宏任务)

image.png

image.png

console.log(100);
// 宏任务
setTimeout(() => console.log(200), 0);

// 微任务
Promise.resolve().then(() => console.log(300));

console.log(400);
// 100 400 300 200

总结:

  1. 执行调用栈中 JS
  2. 执行最早的一个宏任务
  3. 执行所有微任务
  4. 尝试渲染
  5. 转到步骤1

事件循环:微任务和宏任务

JS-Web-API

DOM

vue和react封装了DOM的操作

带着问题学习DOM

  • DOM是哪种数据结构
  • DOM操作的常用API
  • attr和property的区别
  • 如何一次性插入多个DOM节点,考虑性能

知识点

  • 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 这样子。
  • 修改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

image.png

// 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;

参考

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(完成)响应内容解析完成

image.png

  • 跨域:同源策略,跨域解决方案

    • 浏览器的同源策略

      • 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(访问控制允许来源)、
    • 解决跨域方案

      • 纯服务器

        • 服务器 配置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
              // }
              }
              ```

              ![image.png](images/354a3d8e4e704d218b0cf0c54192ba0f~tplv-k3u1fbpfcp-watermark.image.png)
          • 缺点

            • 只支持GET
            • 安全性不够
              • JSONP暴露了很多漏洞,它假定了传送回来的代码是可信的,进一步暴露了CSRF(跨站点请求伪造)漏洞。
            • 没有错误处理,调试困难
      • 纯前端

        • 代理

思考🤔

  • 是不是可以通过跨域的方法,谁都可以做一个山寨淘宝出来?如果是这样,那么跨域好像就没有什么意义了吧。虽然有同源策略限制,但是大家都知道解决这个限制的方法,那就相当于没有限制?

AJAX的一些插件

  • jQuery
  • Fetch
    • 注意: 当接收到一个代表错误的 HTTP 状态码时,从 fetch() 返回的 Promise 不会被标记为 reject,  即使响应的 HTTP 状态码是 404 或 500。相反,它会将 Promise 状态标记为 resolve (但是会将 resolve 的返回值的 ok 属性设置为 false ),仅当网络故障时或请求被阻止时,才会标记为 reject。
  • axios

存储

  • 特点

    • 存储大小,最大4k
    • 随着http请求,
  • 用途

    • 会话状态管理(如用户登录信息、购物车、游戏分数或其他需要记录的信息)
    • 个性化设置(如用户的个性化设置,主题等)
    • 浏览器行为跟踪(如跟踪分析用户行为等)
  • 观点

    • 现在随着现代浏览器开始支持各种各样的存储方式,Cookie 渐渐被淘汰。由于服务器指定 Cookie 后,浏览器的每次请求都会携带 Cookie 数据,会带来额外的性能开销(尤其是在移动环境下)。新的浏览器API已经允许开发者直接将数据存储到本地,如使用 Web storage API (本地存储和会话存储)或 IndexedDB 。
  • 安全

    • JavaScript 通过 Document.cookie 访问 Cookie

      • JavaScript可以通过跨站脚本(xss)来窃取cookie,

      • 跨站请求伪造(CSRF)

          [维基百科](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 仍然有效(还没有其它验证步骤),你银行里的钱很可能会被自动转走。有一些方法可以阻止此类事件的发生:

参考美团文章

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

参考

堆栈