前言
因篇幅原因,将分为两篇文字总结。 内容将覆盖防抖节流(完成)、作用域(已复习)笔试题作用域、预编译(已复习)、闭包(完成)、this指向问题(完成)、new 一个对象经历了什么(完成)、深拷贝浅拷贝(完成)、js类型(完成)、js事件流事件处理程序事件委托(已完成)、隐式转换显示转换(完成)、原型链(完成)、继承(完成)、dns预获取(完成)、promise(完成)、js事件执行机制宏任务,微任务(完成)、async await(完成) js事件思维导图
《js面试题一》
《js面试题二》
1.【重点】下列方法分别输出什么?
考点:引用,赋值,运算符的优先级
代码语言:javascript复制var a = {n:1};
var b = a;
a = {n:2};
a.x = a ;
console.log(a.x);
console.log(b.x);
代码语言:javascript复制var a = {n:1};
var b = a;
a.x = a = {n:2};
console.log(a.x);
console.log(b.x);
答案地址
2. 【重点】js的数据类型有哪些?
按照储存类型分类:
基本类型(基本数据类型都储存在栈中(stack))
undefined
Null
Boolean
Number
String
Symbol
(ES6中新增的)Symbol是一个独一无二的值 `javascript let s = Symbol(); typeof s // "symbol"
```javascript
let s1 = Symbol('foo');
let s2 = Symbol('bar');
s1.toString() // "Symbol(foo)"
s2.toString() // "Symbol(bar)"
代码语言:txt复制引用类型(object 类型)
- `Object`
- `Array`
- `Date`
- `RegExp`
- `Function`等等
**引用类型**的**值**是**储存在堆中**(heap),但是**栈内存**中**保存**着一个堆内存的**对象**的**引用也可以说是那个对象的指针(即地址)**,所以我们常常实际操作的是引用而不是操作实际的对象!
### 3. 【重点】为什么基本数据类型存在栈中,而引用数据类型存在堆中呢?
- 基本数据类型大小可控,应用类型数据大小不可控
- 栈的速度比堆内存的速度块
[答案地址](https://blog.csdn.net/ws9029/article/details/107058444)
### 4. 【重点】项目中见到的状态码(200、301、400、401、403、404、408、500)分别指什么意思?
主要记住客户端错误的状态码
状态码分类:
| 分类 | 分类描述 |
|:----|:----|
| 2** | 请求成功 |
| 3** | 重定向(需要进一步的操作以完成请求) |
| 4** | 客户端错误(请求包含语法错误或无法完成请求) |
| 5** | 服务器错误(服务器在处理请求的过程中发生了错误) |
具体状态的含义:
| 状态码 | 含义 |
|:----|:----|
| 200 | 请求成功 |
| 301 | 重定向( 资源(网页等)被永久转移到其它URL) |
| 400 | 客户端请求的语法错误(服务器无法理解) |
| 401 | 要求用户的身份认证(例如:token过期) |
| 403 | 权限不足 |
| 404 | 找不到资源 |
| 408 | 请求超时 |
| 500 | 服务器内部错误 |
### 防抖节流
使用场景:js中的一些事件如浏览器的resize、scroll,鼠标的mousemove、mousevoer,input输入框的keypress等事件在触发时,会不断的调用绑定在事件上的回调函数,浪费资源、消耗前端性能
- 防抖函数
> 在高频事件被触发n秒后再执行回调,如果n秒内高频事件再次被触发,则重新计算时间
>
> 思路:每次触发事件时都取消之前的延时调用方法
>
应用场景:
- 浏览器的resize事件,根据调整浏览器窗口大小,使用js计算响应式布局。使用防抖控制函数,可以判断当用户停止调整大小时,再计算布局。
- 绑定输入框的 keypress 事件,根据用户的输入,向服务器发送请求以提供搜索选项。使用防抖控制函数,可以判断当用户停止输入的时候,再发送请求。
```javascript
// 实现 1
// fn 是需要防抖处理的函数
// wait 是时间间隔
function debounce(fn, delay) {
代码语言:txt复制// 通过闭包缓存一个定时器 id
代码语言:txt复制var timer = null;
代码语言:txt复制// 将 debounce 处理结果当作函数返回
代码语言:txt复制// 触发事件回调时执行这个返回函数
代码语言:txt复制return function (...args) {
代码语言:txt复制 // 如果已经设定过定时器就清空上一次的定时器
代码语言:txt复制 if (timer) {
代码语言:txt复制 clearTimeout(timer);
代码语言:txt复制 }
代码语言:txt复制 // 开始设定一个新的定时器,定时器结束后执行传入的函数 fn
代码语言:txt复制 timer = setTimeout(() => {
代码语言:txt复制 fn.apply(this, args)
代码语言:txt复制 }, delay);
代码语言:txt复制};
}
// 测试
function say(event) {
代码语言:txt复制console.log(event,"防抖执行了!")
}
document.onmousemove = debounce(say, 100)
代码语言:txt复制- 节流函数
> 每隔一段时间,只执行一次函数。
>
> 思路:用时间戳来判断是否已到执行时间,记录上次执行的时间戳,然后每次触发事件执行回调,回调中判断当前时间戳距离上次执行时间戳的间隔是否已经达到时间差(Xms) ,如果是则执行,并更新上次执行的时间戳
>
应用场景:
- 利用`scroll`事件实现图片或数据根据 scrollTop 的值要实现懒加载的情况
- 判断是否滑动到底部,然后需要更新数据的情况
```javascript
// 节流
function throttle(fn, wait) {
代码语言:txt复制let previous = 0;
代码语言:txt复制return function (...args) {
代码语言:txt复制 let now = new Date;
代码语言:txt复制 if (now - previous > wait) {
代码语言:txt复制 previous = now;
代码语言:txt复制 fn.apply(this, args);
代码语言:txt复制 }
代码语言:txt复制}
}
// 测试
function say(event) {
代码语言:txt复制console.log(event, "节流执行了!")
}
document.onmousemove = throttle(say, 1000)
代码语言:txt复制[参考链接](https://muyiy.cn/blog/7/7.2.html#原理及实现)
### 作用域、作用域链、执行上下文、预编译
**==js是一门基于静态作用域的语言==**
[自己得总结](https://note.youdao.com/web/#/file/WEBfb8ef83e3bfe3b33f18598a1e35d4bbc/markdown/WEBf94b67e2ee354fc26049acc8ac94a852/)
[别人的总结](https://juejin.cn/post/6844904068888920071)
### 闭包
- 闭包
> 定义:能读取另一个函数作用域中变量的函数,通常是在嵌套函数中实现的;
>
> 应用场景:闭包随处可见,一个Ajax请求的成功回调,一个事件绑定的回调方法,一个setTimeout的延时回调,或者一个函数内部返回另一个匿名函数,这些都是闭包。简而言之,无论使用何种方式对函数类型的值进行传递,当函数在别处被调用时都有闭包的身影。
>
作用:
1. **可以在函数外部读取闭包函数内部作用域的变量**
2. **可以让这些变量始终保持在内存中,ajax中请求成功的回调函数就利用了闭包的这个特效**
3. **封装私有变量** [示例地址](https://www.jianshu.com/p/6c75ef57f74d)
缺点以及解决方法:
1. 由于闭包会使函数中的变量都被保存在内存中,内存消耗会很大,滥用闭包会造成网页性能问题,在IE浏览器中可能导致内存泄漏
- 解决方法:在退出函数之前把不使用的局部变量全部删除,
[参考博客阮一峰](https://www.ruanyifeng.com/blog/2009/08/learning_javascript_closures.html)
[参考博客](https://zhuanlan.zhihu.com/p/29157822)
### 【重要】js中this的指向(非箭头函数的四种绑定和箭头函数的this指向)
> **普通函数(非箭头函数)的四种绑定分类**基于****调用点,也就函数被调用的地方;**箭头函数**的this值是函数**创建时**所在的**词法作用域**中的**this**
>
##### 四种this绑定(非箭头函数)
> 四种this绑定的情况:**默认绑定**、**隐式绑定**、**显示不对**、**关键字new绑定**
>
- **默认绑定**
例子:
```javascript
function foo() {
console.log(this.bar);
}
var bar = "bar1";
var o2 = { bar: "bar2", foo: foo };
var o3 = { bar: "bar3", foo: foo };
foo(); // "bar1" – 默认绑定
代码语言:txt复制foo()这种调用方法,就是默认绑定。如果在非严格模式下,this就是全局对象,浏览器当中就是window。而如果在严格模式(use strict)下,this就会是undefined。
- **隐式绑定**
```javascript
function foo() {
console.log(this.bar);
}
var bar = "bar1";
var o2 = { bar: "bar2", foo: foo };
var o3 = { bar: "bar3", foo: foo };
foo(); // "bar1" – 默认绑定
o2.foo(); // "bar2" – 隐式绑定
o3.foo(); // "bar3" – 隐式绑定
代码语言:txt复制o2.foo()和o3.foo()这两种调用方法,都是隐式绑定。Foo是作为o2和o3的方法而调用的,那么谁调用foo,this就指向谁。在上面的例子中,o2.foo()中的this指向o2,因此this.bar就是o2当中的bar: “bar2”;同理,o3.foo()打印出来的就是o3中的”bar3”。
- **显示绑定**
例子:
```javascript
function foo() {
console.log(this.bar);
}
var bar = "bar1";
var obj = { bar: "bar2" };
foo(); // "bar1" 默认绑定
foo.call(obj); // "bar2" 显式绑定,使用obj作为"this"
代码语言:txt复制**如果foo是通过call、apply或者bind调用的,那么这种调用就是显式绑定。这种绑定中,this的指向就是这三个函数中传递的第一个参数。**
- **关键字 new 绑定**
例子:
```javascript
function foo() {
this.baz = "baz";
console.log(this.bar " " baz);
}
var bar = "bar";
var baz = new foo();
代码语言:txt复制如果把new这个关键字放在一个函数调用的前面,JS编译器会做这四件事情:
1. 创建一个新的空对象
2. 把这个新对象链接到原型对象上
3. 这个对象被绑定为 `this`
4. 如果这个函数不返回任何东西,那么就会默认`return this`
==**从这四步可以看出,如果在函数调用前面加上new,那么这个函数中的this就是这个新的对象。**==
上面的例子,最终会输出undefined undefined。这是因为baz这个变量并没有bar这个属性,而baz此时只被定义,没有被赋值,因此baz也是undefined。
##### 箭头函数的this指向
> **箭头函数会无视以上所有的规则,this的值就是函数创建时候所在的词法作用域中的this,而和调用方式无关**
>
>
例子:
```javascript
function Person(){
this.age = 0;
setTimeout(function () {
代码语言:txt复制console.log(this.age); // 输出undefined
}, 1000);
}
var p = new Person();
代码语言:txt复制```javascript
function Person(){
this.age = 10;
setTimeout(()=> {
代码语言:txt复制console.log(this.age); // 输出10
}, 1000);
}
var p = new Person();
代码语言:txt复制在上面没有使用箭头函数的例子当中,setTimeout内部的函数是被global调用的,而global没有age这个属性,因此输出undefined。
第二个例子使用了箭头函数,this就会使用lexical scope中的this,就是Person,因此输出10。
##### 绑定优先级
如果多重绑定规格都适用,那么**绑定规则**的**优先级顺序**是这样的:
1. **箭头函数**
2. **关键字 new 绑定**
3. **显示绑定**
4. **隐式绑定**
5. **默认绑定**
箭头函数优先级最高,会无视2-5绑定规则。而默认绑定优先级最低,只有其他绑定都不使用的时候,才会使用默认绑定。
[参考链接](https://zhuanlan.zhihu.com/p/82504422)
### 【重要】new 一个对象经历了什么过程?(答案未知)
1. **创建一个空对象**
2. **将构造函数的****`this`****赋给新对象(因此this就指向了这个新对象)**
3. **执行构造函数中的代码(为这个新对象添加属性)**
4. **如果这个函数有返回值,则返回;否则,就会默认返回新对象**
[参考链接](https://zhuanlan.zhihu.com/p/84605717)
**参考红宝书第四版p222(第8章-8.2创建对象-8.2.3构造函数模式)**
[用代码实现的例子1](https://zhuanlan.zhihu.com/p/84605717)
[用代码实现的例子2](https://juejin.cn/post/6844903704663949325)
### 【重点】浅拷贝、深拷贝
> 浅拷贝:**只复制指向某个对象的指针,不复制对象本身,新旧对象还是共享同一块内存。**(重新在堆中创建内存,拷贝前后对象的基本数据类型互不影响,但拷贝前后对象的引用类型因共享同一块内存,会相互影响。)
>
> 深拷贝:**另外创造一个一模一样的对象,新对象跟原对象不共享内容,修改新对象不会影响到原对象的值**(从堆内存中开辟一个新的区域存放新对象,对对象中的子对象进行递归拷贝,拷贝前后的两个对象互不影响。)
>
- 浅拷贝的实现方法:
1. `Object.assign()`
2. `...` 展开运算符
3. `Array.prototype.concat()` 方法
4. `Array.prototype.splice()` 方法
- 深拷贝的方法:
1. `JSON.parse(JSON.stringify())`
2. 手写递归方法:深度克隆原理:**遍历对象、数组直到里面都是基本数据类型,然后在去复制,就是深拷贝了**
### 【重点】事件流(事件冒泡、事件)
> 事件流模拟了事件接收的顺序,IE 将支持事件冒泡流,而 Netscape Communicator将支持事件捕获流。
>
**Dom事件流分为三个阶段:事件捕捉、达到目标、事件冒泡。**
第一步:事件捕捉最先发送,在这一步可以在事件到达最终目标前拦截事件。
第二步:实际的目标元素接收到事件,到达目标元素。
第三步:最后一个阶段是冒泡,最迟在这个阶段响应事件
##### 事件冒泡
> 从最具体的元素(文档中最深的节点)开始触发,然后向上传播到没有那么具体的元素(文档)。
>
### 【重点】事件处理程序
三种绑定事件处理程序的方式:
- html事件处理程序:直接在 html 标签里面绑定事件处理程序
- 通过js代码获取元素,然后给元素添加事件处理程序的方法,例如:
```javascript
//在元素的作用域中运行,即 this 等于元素
let btn = document.getElementById("myBtn");
btn.onclick = function() {
代码语言:txt复制console.log(this.id); // "myBtn"
};
代码语言:txt复制- 通过 `addEventLister()` :**接收3个参数:事件名、事件处理函数和一个布尔值**,true 表示在捕获阶段调用事件处理函数,false(默认值)表示在冒泡阶段调用事件处理程序 例如:
```javascript
let btn = document.getElementById("myBtn");
btn.addEventListener("click", () => {
代码语言:txt复制console.log(this.id);
}, false);
代码语言:txt复制
写在最后
我是 AndyHu,目前暂时是一枚前端搬砖工程师。
文中如有错误,欢迎在评论区指正,如果这篇文章帮到了你,欢迎点赞和关注呀