JavaScript 基础知识

一、变量类型和计算

1. 值类型和引用类型

  • 值类型:值是基础类型,包括number string boolean Symbol
  • 引用类型:值是对象,包括null(特殊的应用类型)。

值类型和引用类型的区别:

  • 值类型存储:变量(key)和值(value)都存储在栈中
  • 值类型存储:变量(key)和值(value)存储在栈中,此时的值(value)存储的是对象的在堆中的内存地址,对象存储在堆中。null指向的是一个空的地址

2. 类型判断

(1)typeof 运算符

  • 识别所有值类型
  • 识别函数
  • 判断是否是引用类型object(不可再细分)
// 判断所有值类型
let a; // typeof a === 'undefined'
const str = "abc"; // typeof str === 'string'
const n = 100; // typeof n === 'number'
const b = true; // typeof b === 'boolean'
const s = Symbol("s"); // typeof s === 'symbol'

// 判断引用类型
const a = {}; // typeof a === 'object'
const b = null; // typeof b === 'object'

// 判断方法
function a() {} // typeof a === 'function'
1
2
3
4
5
6
7
8
9
10
11
12
13

(2)深拷贝

  • 注意判断是值类型和引用类型
  • 注意判断是数组还是对象
  • 递归
function deepClone(param = {}) {
    if (param === null || typeof !== 'object') {
        return param;
    }
    let result;
    if (param instanceof Array) {
        result = []
    } else {
        result = {}
    }
    for (let key in param) {
        // 保证 key 不是原型上的属性
        if (param.hasOwnProperty(key)) {
            // 递归
            result[key] = deepClone(param[key])
        }
    }

    return result
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

3. 变量计算问题

(1)+运算符

const a = 100 + 1; // 101
const b = 100 + "1"; // "1001"
const c = 100 + true; // 101
const d = 100 + false; // 100
const e = true + "1"; // "true1"
1
2
3
4
5

注意:+会进行类型转换

(2)==运算符

100 == "100"; // true
0 == ""; // true
0 == false; // true
false == ""; // true
null == undefined; // true
1
2
3
4
5

注意:如何使用===,以上结果相反,==会进行类型转换。因此,建议除了== null之外,其他一律用===

const obj = { x: 100 };
if (obj.a == null) {
}

// 相当于:
if (obj.a == null || obj.a === undefined) {
}
1
2
3
4
5
6
7

(2)if 语句和逻辑运算符

使用 if 进行逻辑判断时,会对变量进行类型转换

以下概念非官方概念

  • truly变量:!!a === true的变量
  • falsely变量:!!a === false的变量

当为truly变量时,if条件为真,当falsely变量时,条件为假

以下是falsely变量,除此之外都是truly变量

!!0 === false;
!!NaN === false;
!!"" === false;
!!null === false;
!!undefined === false;
!!false === false;
1
2
3
4
5
6

二、原型和原型链

1. class 和继承

2. 原型

  • 每一个class都有显示原型prototype
  • 每个实例都有隐式原型__proto__
  • 实例的__proto__指向对应classprototype

3. 原型链

5f479a8847075e51dca74720d1f5fc2a.png

4. 类型判断 instanceof

instanceof 运算符返回一个布尔值,表示对象是否为某个构造函数的实例。 instanceof 的原理是检查右边构造函数的 prototype 属性,是否在左边对象的原型链上。

因此,下面两种写法是等价的。

v instanceof Vehicle;
// 等同于
Vehicle.prototype.isPrototypeOf(v);
1
2
3

注意,instanceof 运算符只能用于对象,不适用原始类型的值。

原型对象的 constructor 属性

  • prototype 对象有一个 constructor 属性,默认指向 prototype 对象所在的构造函数。
  • 由于 constructor 属性定义在 prototype 对象上面,意味着可以被所有实例对象继承。
Student.prototype.constructor === Student; // true
student.__proto__.constructor === Student; // true
1
2

三、作用域和闭包

1. 作用域

作用域: 当前的执行上下文,变量(变量名,函数名)或表达式生效的范围 变量对象: 每一个执行环境都有一个变量对象,包括变量和函数。函数的执行环境的变量对象还包括arguments 对象和形参。 作用域链: 每一个执行环境都有一个作用域链,本质上是一个指向变量对象的指针列表,它只引用但不实际包含变量对象。作用域链的最前端,始终是当前正在执行的代码所在环境的变量对象。 作用域的查找机制: 只能是向上找,不能向下找

2. 闭包

一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(即函数内部引用了外部变量),这样的组合就是闭包(closure)。也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。

自由变量查找规则:从函数定义的地方开始,向上级作用域查找,而不是在执行的地方

四、异步基础

1. 单线程和异步

  • JS 是单线程语言,只能同时做一件事儿
  • 浏览器和 nodejs 已支持 JS 启动进程,如 Web Worker
  • JS 和 DOM 渲染共用同一个线程,因为 JS 可修改 DOM 结构
  • 遇到等待(网络请求,定时任务)不能卡住
  • 需要异步
  • 回调 callback 函数形式

2. 异步和同步

  • 基于 JS 是单线程语言
  • 异步不会阻塞代码执行
  • 同步会阻塞代码执行

3. 异步的应用场景

  • 处理耗时任务,如网络请求
  • 定时任务

4. Promise

解决callback hell(回调地狱)问题而产生,嵌套调用 -> 流式调用

五、异步进阶

1. event loop

(1)背景

  • JS 是单线程运行的
  • 异步要基于回调来实现
  • event loop 就是异步回调的实现原理

(2)event loop 过程

12e1f02882b0bd3273f64e69f5a2a63f.png

  • 同步代码,一行一行放在Call Stack中执行
  • 遇到异步,会先“记录”下来,等待异步回调触发(定时、网络请求等结束)
  • 时机到了,就移动到Callback Quene
  • 如果Call Stack为空(即同步代码执行完毕),Event Loop开始工作
  • 轮询查找Callback Quene,如有任务则移动到Call Stack中执行
  • Event Loop继续轮询查找

2. promise 进阶

  • 三种状态
  • 状态的表现和变化
  • thencatch对状态的影响

(1)三种状态

  • pending 等待(正在处理)
  • resolved 解决(成功)
  • rejected 拒绝(失败)

状态的变化和特点:

  1. pending -> resolved 或 pending -> rejected
  2. resolved 和 rejected 互斥,只有一种结果
  3. 变化不可逆

(2)状态的表现和变化

  • pending -> resolved:触发then的回调
  • pending -> rejected:触发catch的回调
  • then函数正常返回状态为resolvedpromise,里面有报错则返回状态为rejectedpromise
  • catch函数正常返回状态为resolvedpromise,里面有报错则返回状态为rejectedpromise

代码演示:

// Promise.resolve() 返回一个状态为 resolved 的 Promise
Promise.resolve()
  .then(() => {
    console.log(1);
    throw new Error("err"); // 抛出异常,then 返回状态为 rejected 的 Promise
  })
  .catch(() => {
    console.log(2); // 未报错,catch 返回状态为 resolved 的 Promise
  })
  .then(() => {
    console.log(3); // 未报错,then 返回状态为 resolved 的 Promise
  });

// 结果:
// 1
// 2
// 3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

3. async/await

(1)背景

  • 异步回调 callback hell
  • Promise then catch链式调用,但也是基于回调函数
  • async/await是同步语法,彻底消灭回调函数

(2)使用

(3)async/await 和 Promise 的关系

  • 执行async函数,返回的是Promise对象
  • await相当于Promisethen
  • try...catch可捕获异常,代替了Promisecatch

代码演示:

async function fn() {
    return 200;   // 最后会转换成 return Promise.resolve(200)
    // return Promise.resolve(200),和上面的结果一样
}

fn(); // 返回 Promise.resolve(200)

!(async function() {
    // 以下三种结果都是一样
    const d1 = await fn();    // 200,fn()返回的是 Promise.resolve(200)
    const d2 = await 200;   // 200,最终会转换成 await Promise.resolve(200)
    const d3 = await Promise.resolve(200); // 200


    const p1 = Promise.reject('err1')
    try {
        const res1 = await p1;
    }catch(e => {
        console.log(e) // err1
    })

    const p2 = Promise.reject('err2');  // rejected 状态,会报错
    const res2 = await p2;  // 若以 await 相当于 then 来看,then不会执行,所以这一步没有执行
    console.log('res2', res2); // 不会打印
})()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

(4)异步的本质

  • async/await 是解决异步回调的解决方案,是一种语法糖
  • async/await 并不能改变 JS 单线程的本质,还是得有基于 event loop 事件循环的异步回调

代码演示:

async function async1() {
  console.log("async1 start"); // 2
  await async2();
  console.log("async1 end"); // 5
  // await 的后面,都可以看错是 callback 里的内容,即异步,所有同步代码执行完成后才会执行异步
}

async function async2() {
  console.log("async2"); // 3
}

console.log("script start"); // 1
async1();
console.log("script end"); // 4
1
2
3
4
5
6
7
8
9
10
11
12
13
14

(5)for...of 的使用

同步循环,for...in,for,forEach,一次性遍历所有元素 异步循环,for...of,会等待异步执行完毕后才继续下一轮循环

4. 微任务/宏任务

(1)什么是宏任务,什么是微任务

  • 宏任务:setTimeout,setInterval,Ajax,DOM事件
  • 微任务:Promise,async/await
  • 微任务先于宏任务执行

(2)event loop 和 DOM 渲染

42cdee3f1eb77af191d7ac6ccc90ed37.png

  • 每次Call Stack空闲时(即每次轮询结束),即同步任务执行完毕
  • 尝试渲染DOM,若此时DOM结构有变化则渲染DOM,若否则触发Event Loop

(3)微任务和宏任务的区别

  • 宏任务:DOM 渲染后触发,如 setTimeout
  • 微任务:DOM 渲染钱触发,如 Promise

六、Web API

1. DOM

DOM(Document Object Model 文档对象模型)

(1)DOM 的本质

  • xml
  • html
  • 树(DOM 树)

(2)DOM 节点操作

1. 节点获取
  • getElementById
  • getElementsByTagName
  • getElementsByClassName
  • querySelectorAll
2. attribute

标签上的属性,如<p item-data="dddd" style="font-size: 12px;">中的item-datastyle

可以通过以下 API 进行读写:

  • getAttribute
  • setAttribute
p.setAttribute("item-data", "aaaa"); // <p item-data="aaaa"></p>
p.getAttribute("item-data"); // aaaa
1
2
3. property

HTMLElment对象的property属性,即标签上的attribute,可以通过 JS 读写

<p class="a"></p>;

p.className; // a
p.style.width = "12px"; // <p class="a" style="width:12px;"></p>
1
2
3
4
4. property 和 attribute 的区别
  • propertyDOM 中的属性,是 JavaScript 里的对象;
  • attributeHTML 标签上的特性,它的值只能够是字符串;
  • attributes 是属于 property 的一个子集
  • 两者的修改都可能引起 DOM 重新渲染(attribute 的修改一定会引起 DOM 重新渲染)

(3)DOM 结构操作

1. 新增节点
  • createElement,创建新节点
  • appendChild,将新节点插入到某个容器,如果是现有节点则移动
2. 获取子元素列表,获取父元素

childNodes属性

<div id="parent">
  <p>a</p>
  <p>b</p>
  <p>c</p>
</div>;

const div = document.getElementById("parent");

// 1. 因为 childNodes 是一个类数组,需要用 slice 变成真正的数组
// 2. childNodes 中包含一些 text,需要把真正的 p 标签过滤出来,nodeType为1就是真正的标签
const pList = Array.prototype.slice
  .call(div.childNodes)
  .filter((child) => child.nodeType === 1);
1
2
3
4
5
6
7
8
9
10
11
12
13
3. 删除元素

removeChild

div.removeChild(childNode);
1

(4)DOM 性能

  • DOM 操作非常“昂贵”,避免频繁的 DOM 操作
  • 对 DOM 查询做缓存
  • 将频繁操作改为一次性操作
// 将频繁操作改为一次性操作

const listNode = document.getElmentById("list");

const frag = document.createDocumentFragment();

for (let x = 0; x < 10; x++) {
  const li = document.createElement("li");
  li.innerHTML = "List Item " + x;
  frag.appendChild(li);
}

listNode.appendChild(frag);
1
2
3
4
5
6
7
8
9
10
11
12
13

2. BOM

BOM-Browser Object Model

  • navigator
  • screen
  • location
  • history

(1)navigator

获取 userAgent 信息:

const ua = navigator.userAgent;
const isChrome = ua.indexOf("Chrome");
1
2

(2)screen

获取屏幕大小

  • screen.width
  • screen.height

(3)location

获取地址信息:

  • location.href:整个网址信息,包括路径参数,hash 等
  • location.protocol:协议,如http:https:
  • location.hostname:主机名,如何www.baidu.com
  • location.pathname:路径,如/index.html/user
  • location.search:查询参数,?后面的key value
  • location.hashhash参数,#后面的参数

(4)history

路由信息:

  • history.back()
  • history.forward()

3. 事件

  • 概述
  • 事件冒泡
  • 事件代理

(1)概述

DOM 的事件操作(监听和触发),都定义在 EventTarget 接口。所有节点对象都部署了这个接口 EventTarget 实例的三个方法:

  • addEventListener
  • removeEventListener
  • dispatchEvent
addEventListener
target.addEventListener(type, listener[, useCapture]);
1

该方法接受三个参数。

  • type:事件名称,大小写敏感。

  • listener:监听函数。事件发生时,会调用该监听函数。

  • useCapture:布尔值或对象,为布尔值时表示监听函数是否在捕获阶段capture),默认为 false(监听函数只在冒泡阶段被触发)。为对象时,有以下属性:

    • capture:布尔值,表示该事件是否在捕获阶段触发监听函数。
    • once:布尔值,表示监听函数是否只触发一次,然后就自动移除。
    • passive:布尔值,表示监听函数不会调用事件的 preventDefault 方法。如果监听函数调用了,浏览器将忽略这个要求,并在监控台输出一行警告。
removeEventListener
target.addEventListener(type, listener[, useCapture]);
1

注意:

  • 必须和使用 addEventListener 同一个节点
  • listener 不能是匿名函数
  • useCapture 必须和 addEventListener 时一样
dispatchEvent
target.dispatchEvent(event);
1

该方法返回一个布尔值,只要有一个监听函数调用了 Event.preventDefault(),则返回值为 false,否则为 true

(2)事件传播

一个事件发生后,会在子元素和父元素之间传播(propagation)事件的传播有三个阶段:

  • 第一阶段:从 window 对象传导到目标节点(上层传到底层),称为“捕获阶段”(capture phase)。
  • 第二阶段:在目标节点上触发,称为“目标阶段”(target phase)。
  • 第三阶段:从目标节点传导回 window 对象(从底层传回上层),称为“冒泡阶段”(bubbling phase)。

(3)事件代理

由于事件会在冒泡阶段向上传播到父节点,因此可以把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件。这种方法叫做事件的代理(delegation)。

  • 代码简洁
  • 减少浏览器内存占用
  • 但不要滥用,如适用于 list item 的 click 事件监听
// 通用的事件绑定函数
function bindEvent(elem, type, selector, callback) {
  if (callback == null) {
    callback = selector;
    selector = null;
  }

  elem.addEventListener(type, (e) => {
    let target = e.target;
    if (selector) {
      // 需要代理
      if (target.matches(selector)) {
        callback.call(target, e);
      }
    } else {
      // 不需要代理
      callback.call(target, e);
    }
  });
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

4. Ajax

  • XMLHttpRequest
  • 状态码
  • 跨域:同源策略,跨域解决方案

(1)使用 XMLHttpRequest 手写简易的 post 和 get 方法

// GET 请求
const xhr = new XMLHttpRequest();
// 第三个参数 false 表示异步
xhr.open("GET", "/data/test.json", true);
xhr.onreadystatechange = function () {
  if (xhr.readyState === 4) {
    if (xhr.status === 200) {
      console.log(xhr.responseText);
    }
  }
};
// get 请求不传参数时,填null
xhr.send(null);

// POST 请求
const xhr = new XMLHttpRequest();
// 第三个参数 false 表示异步
xhr.open("POST", "/login", true);
xhr.onreadystatechange = function () {
  if (xhr.readyState === 4) {
    if (xhr.status === 200) {
      console.log(xhr.responseText);
    }
  }
};

const postData = {
  userName: "张三",
  password: "123456",
};
// 需要转成字符串
xhr.send(JSON.stringify(postData));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

(2)状态码

xhr.readyState状态码:

  • 0 - (未初始化)还没有调用send方法
  • 1 - (载入)已经调用send方法,正在发送请求
  • 2 - (载入完成)send方法执行完成,已经接收到全部响应内容
  • 3 - (交互)正在解析响应内容
  • 4 - (完成)响应内容解析完成,可以在客户端调用

xhr.status状态码:(即http状态码)

  • 2xx - 表示成功处理请求,如 200
  • 3xx - 需要重定向,浏览器直接自动跳转,如 301(永久重定向)、302(临时重定向)、304(资源未改变,可以使用浏览器缓存)
  • 4xx - 客户端请求错误,如 404(资源未找到)、403(无权限)
  • 5xx - 服务器端错误

(3)跨域

1. 同源策略
  • 出于安全性的考虑,ajax 请求时,浏览器要求当前网页和 server 必须同源
  • 同源:协议、域名和端口,三者必须一致
  • 跨域的例子,如 前端:http://a.com:8080/;后端:https://b.com/api/xxx

加载图片、css、js 可无视同源策略:

  • <img src="跨域的图片地址"/>,可用于统计打点,可使用第三方统计服务
  • <link href="跨域的 css 地址" />
  • <script href="跨域的 js 地址" />,可实现 JSONP
2. 跨域
  • 所有的跨域,都必须经过server端允许和配合
  • 未经server端允许就实现跨域,说明浏览器有漏洞,危险信号

3. 跨域的方法

  • JSONP
  • CORS
(1)JSONP 跨域

关键点:

  • <script>可绕过跨域限制
  • 服务器可以任意动态拼接数据返回
<script>
  window.callback = function (data) {
    // 跨域信息
    console.log(data); // "{ x: 100, y: 200 }"
  };
</script>

<!-- 通过 script 标签进行跨域请求,返回 callback({ x: 100, y: 200 }) -->
<script src="https://www.abc.com/test.js"></script>

<script>
  window.func = function (data) {
    // 跨域信息
    console.log(data); // "{ x: 100, y: 200 }"
  };
</script>
<!-- 通过 script 标签进行跨域请求,自定义 callback 名称,通过服务器动态拼接,返回 func({ x: 100, y: 200 }) -->
<script src="https://www.abc.com/test.js?callback=func"></script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
(1)CORS 跨域

在服务端中设置:

// 第二个参数填写允许跨域的域名称,不建议直接写 "*"
response.setHeader("Access-Control-Allow-Origin", "http://localhost:8011");
response.setHeader("Access-Control-Allow-Headers", "X-Request-With");
response.setHeader(
  "Access-Control-Allow-Methods",
  "PUT,POST,GET,DELETE,OPTIONS"
);

// 接收跨域的cookie
response.setHeader("Access-Control-Allow-Credentials", "true");
1
2
3
4
5
6
7
8
9
10

4. ajax 常用插件

  • jQuery.ajax()
  • fetch
  • axios

5. 存储

  • cookie 存储于客户端本地
  • 携带于 http 请求头中,本身为浏览器和 server 通讯而设计
  • 被“借用”来进行本地存储
  • 可用document.cookie = '...'来修改

缺点:

  • 存储大小有限制,最大 4KB
  • http 请求时需要发送到服务端,增加请求数据量
  • 只能用 document.cookie = '...'修改,太过简陋

(2)localStorage 和 sessionStorage

  • HTML5 专门为前端存储而设计,最大可存储 5M
  • API 简单易用 setItem getItem
  • 不会随着 http 请求被发送出去
  • 以字符串形式存储

区别:

  • localStorage数据会永久存储,除非代码或手动删除
  • sessionStorage数据只会存在于当前会话,浏览器关闭则清空
  • 一般用localStorage会更多一些

六、HTTP 协议

1. http 状态码

(1)状态码分类

  • 1xx:服务器收到请求
  • 2xx:请求成功,如 200
  • 3xx:重定向,如 302
  • 4xx:客户端错误,如 404(not found)
  • 5xx:服务端错误,如 500

(2)常见状态码

  • 200:请求成功
  • 301:永久重定向(配合locationheaderlocation返回重定向的地址,浏览器自动跳转,如原始资源永久性移除,迁移到另一个地方。或域名更改等)
  • 302:临时重定向(配合locationheaderlocation返回重定向的地址,浏览器自动跳转,如原始资源临时性迁移,随时会改变注意,可能造成网址 URL 劫持)
  • 304:资源未被修改,可以使用浏览器缓存
  • 404:资源未找到
  • 403:禁止访问,没有权限
  • 500:服务器错误
  • 504:网关超时

(3)关于协议和规范

状态码是 http 协议的规范,不能违反规范,如 IE 浏览器

2. http methods

(1)传统的 methods

  • get:获取服务器的数据
  • post:向服务器提交数据

(2)现在的 methods

  • get:获取数据
  • post:新建数据
  • patch/put:更新数据
  • delete:删除数据

3. Restful API

  • 一种新的 API 设计方法(早已推广使用)
  • 传统 API 设计:把每个 url 当做一个功能
  • Restful API 设计:把每个 url 当做一个唯一的资源

如何设计资源

  1. 不使用 url 参数
  2. 用 method 表示操作类型

4. http headers

(1)常见的 Request Headers

  • Accept:浏览器可接收的数据格式
  • Accept-Encoding:浏览器可接收的压缩算法,如 gzip
  • Accept-Languange:浏览器可接收的语言,如 zh-CN
  • Connection:keep-alive:一次 TCP 连接重复使用
  • cookie
  • Host:请求的服务器
  • User-Agent:(简称 UA)浏览器信息
  • Content-Type:发送数据的格式,如 application/json

(2)常见的 Response Headers

  • Content-Type:发送数据的格式,如 application/json
  • Content-length:返回数据的大小,多少字节
  • Content-Encoding:返回数据的压缩算法,如 gzip
  • Set-Cookie:设置 cookie
  • cache-control:缓存控制

(3)缓存相关的 headers

5. http 缓存

(1)关于缓存

  • 缓存:浏览器首次加载资源时,对一些资源保存起来,下次访问时尽量避免网络请求,优先使用缓存
  • 缓存目的:提升加载速度,性能优化
  • 可以被缓存资源:静态资源(js css img)

(2)http 缓存-强制缓存

由服务端设置响应头开启缓存:如 Cache-Control : max-age=3423422Cache-Control的值:

  • max-age:设置缓存有效时间,在时效内时不向服务器请求资源,直接从浏览器缓存读取数据。不在时效内时,重新请求服务器资源,再缓存,如此反复。
  • no-cache:不用缓存,直接请求服务器资源,不管服务器如何处理
  • no-store:不用缓存,也不让服务端做缓存
  • private:只允许客户端(浏览器等最终的客户端),不允许中间路由和代理做缓存
  • public:允许中间的路由、代理做缓存
关于 Expires
  • 同在 Response Headers 中
  • 同为控制缓存过期
  • 已被 Cache-Control : max-age 代替

(3)http-协商缓存

1. 协商缓存策略
  • 服务端缓存策略(由服务端判断是否使用缓存内容)
  • 服务端判断客户端资源,是否和服务端资源一样
  • 一致则返回 304,否则返回 200 和最新的资源

fb44648259ad75e1786f1e44f015f3ce.png

2. 资源标识
  • 在 Response Headers 中,有两种
  • Last-Modified资源的最后修改时间
  • Etag资源的唯一标识(一个字符串,类似人类的指纹)

ecadb541b8bc08c6880e0052173976b7.pngef42dd8cc5e2eea8b46f36fe6a863e8f.png

3. Last-Modified 和 Etag
  • 两者可以共存,会优先使用 Etag
  • Last-Modified 只能精确到秒级
  • 如果资源重复生成,而内容不变,则 Etag 更精确

f936546c29f1e06e07fa6139b01ea861.png

七、开发环境

八、运行环境

1. 网页的加载过程

2. 前端性能优化

3. 手写防抖函数 debounce

(1)场景

  • 监听一个输入框,文字变化后会触发 change 事件
  • 直接用 keyup 事件,则会频繁触发 change 事件
  • 防抖:用户输入结束或暂停时,才会触发 change 事件

(2)代码实现

function debounce(fn, delay = 500) {
  let timer = null;
  return function () {
    // 清除计时器,重新计时
    if (timer) {
      clearTimeout(timer);
    }
    timer = setTimeout(() => {
      fn.apply(this.arguments);
      timer = null;
    }, delay);
  };
}
1
2
3
4
5
6
7
8
9
10
11
12
13

4. 手写节流函数 throttle

(1)场景

  • 拖拽一个元素时,要随时拿到该元素被拖拽的位置
  • 直接用 drag 事件,则会频繁触发,很容易造成卡顿
  • 节流:无论拖拽速度多快,都会以稳定的时间间隔触发一次,如 100ms

(2)代码实现

function throttle(fn, delay = 100) {
  let timer = null;

  return function () {
    // 如果 timer 存在,则忽略不执行,直接返回
    if (timer) {
      return;
    }
    timer = setTimeout(() => {
      fn.apply(this, arguments);
      timer = null;
    }, delay);
  };
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

5. 安全

(1)XSS 跨站请求攻击

1. 场景
  • 一个博客网站,发表一篇博客,其中嵌入<script>脚本
  • 脚本内容:获取 cookie,发送到我的服务器(服务器配合跨域)
  • 发布这篇博客,有人查看它,我轻松查看访问者的 cookie
2. XSS 预防
  • 替换特殊字符,如 < 变为 &lt;> 变为 &gt;
  • <script> 变成 &lt;script&gt;,直接显示,而不会作为脚本执行
  • 前端要替换,后端也要替换,都做总不会出错

(2)XSRF 攻击

1. 场景
  • 你正在购物,看中了某个商品,商品 id 是 100
  • 付费接口是 xx.com/pay?id=100,但没有任何验证
  • 我是攻击者,我看中了一个商品,id 是 200
  • 我向你发送一封电子邮件,邮件标题很吸引人
  • 但邮件正文隐藏<img src="xxx.com/pay?id=200"/>
  • 你一查看邮件,就帮我购买了 id 是 200 的商品
2. XSS 预防
  • 使用 post 接口
  • 增加验证,例如密码,短信验证码,指纹等