目录
- 1.JS中判断数据类型的方法有几种?
- 2.new的执行过程
- 3.es module 和 commonjs 的区别
- 4.webpack plugin 里的插件什么时候被调用到
- 5.与 ES5 相比,React 的 ES6 语法有何不同?
- 6.promise和async的区别
- 7.箭头函数和普通函数有什么区别
- 8.this的指向问题?
- 9.var和let和const的区别。
- 10.变量提升
- 11.es6的新特性
- 12.webpack有单独配置过吗?里面的loader有哪些?css loader是做什么的?如果有一个loader数组,执行顺序是从左到右,还是从右到左?
- 13.前端性能优化
- 14.代码优化相关
- 15.什么是闭包?
- 16.说一下base64
- 17.说一下SVG
- 18.说一下PNG.和JPG有什么不同
- 19.说一下异步
- 20.说一下异步有那些方案
- 21.说一下宏任务微任务
- 22.宏任务有那些,微任务有那些
- 23.说一下call,apply,bind的区别
- 24.js延迟加载的方式有哪些?
- 25.列举javaScript的主要数据类型有哪些?
- 26.Es6有那些继承方法?
- 27.什么是继承,实现的方法
- 28.深浅拷贝
- 29.JSON.parse(JSON.stringify(obj))深拷贝的问题
- 30.伪数组转成真数组的方法(至少两种方法) (js)
- 31.link和import的区别(css引入)
- 32.哪些操作会造成内存泄露(至少三个)(js)
- 33.web网络请求的过程
- 34.主线程,队列
- 35.线程,进程区别
- 36.浏览器优化方案
- 38.什么是作用域和作用域链?
- 39.作用域和执行上下文的区别是什么?
- 40.this指向的各种情况都有什么?
- 41.如何改变this指针的指向?
- 42.垃圾回收机制
- 43.什么是原型、原型链?
- 44.何为防抖和节流?如何实现?
- 45.如何理解同步和异步?
- 46.JS是如何实现异步的?
- 47.什么是AJAX?如何实现?
- 48.实现异步的方式有哪些?
- 49.怎么理解Promise对象?
- 50.怎么理解宏任务,微任务???
- 51.什么是跨域?怎么解决跨域问题?
- 52.DOM事件模型和事件流?
- 53.EventLoop事件循环是什么?
- 54.require/import之间的区别?
- 55.new操作符都做了些什么,手写实现new
- 56.js中的数据类型
- 57.requestAnimationFrame的优势
- 58.箭头函数
- 59.curry
- 60.数组扁平化
- 61.数组去重
1.JS中判断数据类型的方法有几种?
最常见的判断方法:typeof
let num=1
typeof(num)
"number"
判断已知对象类型的方法: instanceof
obj instanceof Object
true
根据对象的constructor判断: constructor
B.constructor == A
可以判断A是否为B的原型,但constructor检测 Object与instanceof不一样,还可以处理基本数据类型的检测
Object.prototype.toString.call()
Object.prototype.toString.call(obj)
"[object Object]"
2.new的执行过程
- 创建一个空对象
- 将对象的_proto_指向构造函数的 prototype
- 将这个构造函数作为这个对象的this
- 返回该对象
3.es module 和 commonjs 的区别
前者是值的引用,后者是值的拷贝。
前者编译时输出接口,后者运行时加载。
4.webpack plugin 里的插件什么时候被调用到
根据webpack里的生命周期函数被订阅
5.与 ES5 相比,React 的 ES6 语法有何不同?
require 与 import
// ES5
var React = require('react');
// ES6
import React from 'react';
export 与 exports
// ES5
module.exports = Component;
// ES6
export default Component;
6.promise和async的区别
- async/await是写异步代码的新方式,以前的方法有回调函数和Promise。
- async/await是基于Promise实现的,它不能用于普通的回调函数。
- async/await与Promise一样,是非阻塞的。
- async/await使得异步代码看起来像同步代码。
7.箭头函数和普通函数有什么区别
- 普通函数中的this总是指向调用它的那个对象
- 箭头函数没有自己的this,他的this永远指向其定义环境,任何方法都改变不了其指向,如call()、bind()、apply()。(正是因为它没有this,所以也就不能用作构造函数,也没有原型对象)
- 箭头函数不能当作构造函数,也就是说,不能使用new命令,否则会报错。
- 箭头函数不能使用yield命令,因此箭头函数不能用作genertor函数。
- 箭头函数没有原型属性。
- 箭头函数不能使用argumen对象,该对象在函数体内不存在。如果要用,可以用rest参数代替。
8.this的指向问题?
- 普通函数中,this指向其函数的直接调用者;
- 箭头函数中,this指向其定义环境,任何方法都改变不了其指向,如call()、bind()等;
- 构造函数中,如果不使用new,则this指向window,如果使用new创建了一个实例,则this指向该实例。
9.var和let和const的区别。
- var声明的变量会挂载在window上,而let和const声明的变量不会
- var声明变量存在变量提升,let和const不存在变量提升
- let和const声明形成块作用域
- 同一作用域下let和const不能声明同名变量,而var可以
- const :一旦声明必须赋值,不能使用null占位、 声明后不能再修改、如果声明的是复合类型数据,可以修改其属性
10.变量提升
- js会将变量的声明提升到js顶部执行,因此对于这种语句:var a = 2;其实上js会将其分为var a;和a =
2;两部分,并且将var a这一步提升到顶部执行。 - 变量提升的本质其实是由于js引擎在编译的时候,就将所有的变量声明了,因此在执行的时候,所有的变量都已经完成声明。
- 3.当有多个同名变量声明的时候,函数声明会覆盖其他的声明。如果有多个函数声明,则是由最后的一个函数声明覆盖之前所有的声明。
11.es6的新特性
- Let const
- 箭头函数
- 模板字符串
- 扩展运算符
- Promise
- Class类
12.webpack有单独配置过吗?里面的loader有哪些?css loader是做什么的?如果有一个loader数组,执行顺序是从左到右,还是从右到左?
- style-loader 将css添加到DOM的内联样式标签style里
- css-loader 允许将css文件通过require的方式引入,并返回css代码
- less-loader 处理less
- sass-loader 处理sass
- postcss-loader 用postcss来处理CSS
- autoprefixer-loader 处理CSS3属性前缀,已被弃用,建议直接使用postcss
- file-loader 分发文件到output目录并返回相对路径
- url-loader 和file-loader类似,但是当文件小于设定的limit时可以返回一个Data Url
- html-minify-loader 压缩HTML
- babel-loader 用babel来转换ES6文件到ES5
从右到左
13.前端性能优化
- 减少请求资源大小或者次数
- 尽量和并和压缩css和js文件。
- 尽量所使用的字体图标或者SVG图标来代替传统png图
- 采用图片的懒加载(延迟加载)
- 能用css做的效果,不要用js做,能用原生js做的,不要轻易去使用第三方插件。
- 使用雪碧图或者是说图片精灵
- 减少对cookie的使用(最主要的就是减少本地cookie存储内容的大小),因为客户端操作cookie的时候,这些信息总是在客户端和服务端传递。如果上设置不当,每次发送
- 合理使用keep-alive
14.代码优化相关
- 在js中尽量减少闭包的使用
- 减少对DOM操作,主要是减少DOM的重绘与回流(重排)
- 在js中避免嵌套循环和"死循环"(一旦遇到死循环,浏览器就会直接卡掉)
- 尽量不要使用全局变量
- 尽量减少使用递归。避免死递归
15.什么是闭包?
闭包就是引用了其他函数作用域中变量的函数,这种模式通常在函数嵌套结构中实现。里面的函数可以访问外面函数的变量,外面的变量的是这个内部函数的一部分。闭包有如下作用:
- 加强封装,模拟实现私有变量;
- 实现常驻内存的变量。
闭包不能滥用,否则会导致内存泄露,影响网页的性能。闭包使用完了后,要立即释放资源,将引用变量指向null。
16.说一下base64
Base64是一种用64个字符来表示任意二进制数据的方法。
图片大的话会污染html结构
17.说一下SVG
- SVG 指可伸缩矢量图形 (Scalable Vector Graphics)
- SVG 用来定义用于网络的基于矢量的图形
- SVG 使用 XML 格式定义图形
- SVG 图像在放大或改变尺寸的情况下其图形质量不会有所损失
- SVG 是万维网联盟的标准
- SVG 与诸如 DOM 和 XSL 之类的 W3C 标准是一个整体
18.说一下PNG.和JPG有什么不同
jpg是有损压缩格式,png是无损压缩格式(PNG支持透明,JPG不支持)
19.说一下异步
线程异步:是多个线程在访问竞争资源时,可以在空闲等待时去访问其它资源(不被阻塞)。
20.说一下异步有那些方案
回调函数 事件监听 promise
21.说一下宏任务微任务
微任务在当前宏任务执行完毕后且执行栈为空时执行,且此过程中产生的微任务也会被放至微任务队列中一起执行。(例如promise的回调就会被放至微任务队列)。
22.宏任务有那些,微任务有那些
-
宏任务
浏览器 Node
setTimeout √ √
setInterval √ √
setImmediate x √
requestAnimationFrame √ x -
微任务
浏览器 Node
process.nextTick x √
MutationObserver √ x
Promise.then catch finally √ √
23.说一下call,apply,bind的区别
-
共同点
- 都是函数的方法。
- 都会通过传递第一个参数改变函数调用的this
- 都可以向函数传递参数。
-
不同点
- call和apply的不同点只有一点,那就是向函数传递参数的方式不同。call是逐个的向函数传递参数,apply是通过将参数以数组的形式传递到函数中
- bind第二个参数是回调函数
24.js延迟加载的方式有哪些?
<script async="async"></script>
<script defer="defer"></script>
- 动态创建
<script>
标签 - iframe
25.列举javaScript的主要数据类型有哪些?
- 基本数据类型:Number/String/Boolean/null/undefined
- 引用数据类型:Object
26.Es6有那些继承方法?
- ES6提供extends继承方法
27.什么是继承,实现的方法
子类拥有父类的特征,而父类没有,父类更通用,子类更具体,(特征包括属性和方法,自身的特性,拥有父类没有的)
- class+extends继承(ES6)
//类模板
class Animal {
constructor(name){
this.name = name
}
}
//继承类
class Cat extends Animal{//重点。extends方法,内部用constructor+super
constructor(name) {
super(name);
//super作为函数调用时,代表父类的构造函数
}//constructor可省略
eat(){
console.log("eating")
}
}
- 原型继承
//类模板
function Animal(name) {
this.name = name;
}
//添加原型方法
Animal.prototype.eat = function(){
console.log("eating")
}
function Cat(furColor){
this.color = color ;
};
//继承类
Cat.prototype = new Animal()//重点:子实例的原型等于父类的实例
- 借用构造函数继承
function Animal(name){
this.name = name
}
function Cat(){
Animal.call(this,"CatName")//重点,调用父类的call方法
}
- 寄生组合式继承(重点)
function SuperType(name){
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
alert(this.name);
}
function SubType(name, age){
SuperType.call(this, name); //第二次调用SuperType()
this.age = age;
}
SubType.prototype = new SuperType(); //第一次调用SuperType()
SubType.prototype.sayAge = function(){
alert(this.age);
}
28.深浅拷贝
- 浅拷贝
- 浅拷贝只是拷贝基本类型的数据,如果父对象的属性等于数组或另一个对象,那么实际上,子对象获得的只是一个内存地址,因此存在父对象被篡改的可能,浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存
- 实现:Object.assign、直接用=赋值、
- 深拷贝
- 深拷贝就是能够实现真正意义上的数组和对象的拷贝。递归调用"浅拷贝"。(深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象)
- 浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象
- 实现:递归、lodash、JSON.stringify,JSON.parse
function deepCopy(obj, cache = new WeakMap()) {
if (typeof obj !== 'object' || obj === null) return obj
// 正则 日期等包装类对象 参考 https://blog.csdn.net/liwusen/article/details/78759373
if ({}.toString.call(obj) === '[object Date]') return new Date(obj.valueOf())
if (obj instanceof RegExp) return new RegExp(obj.source, obj.flags)
// 防止循环引用
if (cache.get(obj)) return cache.get(obj)
const res = Array.isArray(obj) ? [] : {}
cache.set(obj, res)
for (const key in obj) {
if (typeof obj[key] === 'object') {
res[key] = deepCopy(obj[key], cache)
} else {
res[key] = obj[key]
}
}
return res
}
// 测试
const source = {
name: 'Jack',
meta: {
age: 12,
birth: new Date('1997-10-10'),
ary: [1, 2, { a: 1 }],
say() {
console.log('Hello')
}
}
}
source.source = source
const newObj = deepCopy(source)
console.log(newObj.meta.birth == source.meta.birth)
29.JSON.parse(JSON.stringify(obj))深拷贝的问题
- 如果obj里面存在时间对象,JSON.parse(JSON.stringify(obj))之后,时间对象变成了字符串。
- 如果obj里有RegExp、Error对象,则序列化的结果将只得到空对象。
- 如果obj里有函数,undefined,则序列化的结果会把函数, undefined丢失。
- 如果obj里有NaN、Infinity和-Infinity,则序列化的结果会变成null。
- JSON.stringify()只能序列化对象的可枚举的自有属性。如果obj中的对象是有构造函数生成的, 则使
- JSON.parse(JSON.stringify(obj))深拷贝后,会丢弃对象的constructor。
- 如果对象中存在循环引用的情况也无法正确实现深拷贝。
30.伪数组转成真数组的方法(至少两种方法) (js)
- 使用Array.prototype.slice.call();
- Array.from
31.link和import的区别(css引入)
- link是XHTML标签,除了可以引用css样式外还可以定义RSS等事物,但@import是css标签,只能引用css样式。
- link在页面加载的同时加载,而@import是在页面内容加载完成之后加载的。
- link是XHTML标签,没有兼容问题,而@import是在css2.1提出来的,低版本的浏览器不支持。
- 4.link支持使用javascript控制DOM去改变样式,@import不支持。
32.哪些操作会造成内存泄露(至少三个)(js)
- 意外的全局变量引起的内存泄露
- 闭包引起的内存泄露
- 没有清理的DOM元素引用
- 被遗忘的定时器或者回调
33.web网络请求的过程
- 域名解析
- TCP的三次握手
- 建立TCP连接后发起HTTP请求\
- HTTP请求格式
- 服务器响应HTTP请求 浏览器得到html代码
- 浏览器解析html代码,并请求html代码中的资源
三次握手:
概念:指在发送数据的准备阶段,服务器和客户端之间需要三次交互
第一次握手:建立连接时,客户端向服务器发送一个SYN包,并进入SYN_SENT状态,等待服务器确认
第二次握手:当服务器收到客户端的请求后,此时要给客户端给一个确认信息ACK,同时发送SYN包,此时服务器进入 SYN_RECV状态
第三次握手:客户端收到服务器发的ACK+SYN包后,向服务器发送ACK,发送完毕之后,客户端和服务器进入 ESTABLISHED(TCP连接成功)状态,完成三次握手
四次挥手:
概念:所谓四次挥手就是说关闭TCP连接的过程,当断开一个TCP连接时,需要客户端和服务器共发送四个包确认
第一次挥手:客户端发送一个FIN,用来关闭客户端到服务器的数据传输,客户端进入FIN_WAIT_1状态
第二次挥手:服务器收到FIN后,发送一个ACK给客户端,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序 号),服务器进入CLOSE_WAIT状态
第三次挥手:服务器发送一个FIN,用来关闭服务器到客户端的数据传输,服务器进入LAST_ACK状态
第四次挥手:客户端收到FIN后,客户端进入TIME_WAIT状态,接着发送一个AKC给服务器,确认序号为收到序号+1,服务器进入CLOSED状态,完成四次挥手
34.主线程,队列
JS只有一个线程,称之为主线程。而事件循环是主线程中执行栈里的代码执行完毕之后,才开始执行的。所以,主线程中要执行的代码时间过长,会阻塞事件循环的执行,也就会阻塞异步操作的执行。只有当主线程中执行栈为空的时候(即同步代码执行完后),才会进行事件循环来观察要执行的事件回调,当事件循环检测到任务队列中有事件就取出相关回调放入执行栈中由主线程执行。
35.线程,进程区别
进程:是执行中一段程序,即一旦程序被载入到内存中并准备执行,它就是一个进程。进程是表示资源分配的的基本概念,又是调度运行的基本单位,是系统中的并发执行的单位。
线程:单个进程中执行中每个任务就是一个线程。线程是进程中执行运算的最小单位。
36.浏览器优化方案
- 减少 HTTP请求数
- 资源文件加载优先级 重要的放在头部 次要的放在尾部
- 利用浏览器缓存
- 使用 CDN
- 减少重排
- 减少 DOM 操作
- 图标使用 IconFont
- 精灵图
38.什么是作用域和作用域链?
作用域可以理解为一个独立的地盘,可以理解为标识符所能生效的范围。作用域最大的用处就是隔离变量,不同作用域下同名变量不会有冲突。ES6中有全局作用域、函数作用域和块级作用域三层概念。
当一个变量在当前块级作用域中未被定义时,会向父级作用域(创建该函数的那个父级作用域)寻找。如果父级仍未找到,就会再一层一层向上寻找,直到找到全局作用域为止。这种一层一层的关系,就是作用域链 。
39.作用域和执行上下文的区别是什么?
- 函数的执行上下文只在函数被调用时生成,而其作用域在创建时已经生成;
- 函数的作用域会包含若干个执行上下文(有可能是零个,当函数未被调用时)。
40.this指向的各种情况都有什么?
this的指向只有在调用时才能被确定,因为this是执行上下文的一部分。
- 全局作用域中的函数:其内部this指向window:
var a = 1;
function fn(){
console.log(this.a)
}
fn() //输出1
- 对象内部的函数:其内部this指向对象本身:
var a = 1;
var obj = {
a:2,
fn:function(){
console.log(this.a)
}
}
obj.fn() //输出2
- 构造函数:其内部this指向生成的实例:
function createP(name,age){
this.name = name //this.name指向P
this.age = age //this.age指向P
}
var p = new createP("老李",46)
- 由apply、call、bind改造的函数:其this指向第一个参数:
function add(c,d){
return this.a + this.b + c + d
}
var o = {a:1,b:2)
add.call(o,5,7) //输出15
- 箭头函数:箭头函数没有自己的this,看其外层的是否有函数,如果有,外层函数的this就是内部箭头函数的this,如果没有,则this是window。
41.如何改变this指针的指向?
- 可以使用apply、call、bind方法改变this指向(并不会改变函数的作用域)。比较如下:
- 三者第一个参数都是this要指向的对象,也就是想指定的上下文,上下文就是指调用函数的那个对象(没有就指向全局window);
- apply和bind的第二个参数都是数组,call接收多个参数并用逗号隔开;
- apply和call只对原函数做改动,bind会返回新的函数(要生效还得再调用一次)。
42.垃圾回收机制
垃圾回收机制:本来一个函数执行完毕以后定义在内部的变量会被浏览器的垃圾回收机制所收回,但是闭包这样的并不会,因为内部函数依旧引用了a、b所以并不会被收回而是长期驻扎在内存当中
本来一个函数执行完毕以后定义在内部的变量会被浏览器的垃圾回收机制所收回,但是闭包这样的并不会,因为内部函数依旧引用了a、b所以并不会被收回而是长期驻扎在内存当中
43.什么是原型、原型链?
原型:JS声明构造函数(用来实例化对象的函数)时,会在内存中创建一个对应的对象,这个对象就是原函数的原型。构造函数默认有一个prototype属性,prototype的值指向函数的原型。同时原型中也有一个constructor属性,constructor的值指向原函数。
通过构造函数实例化出来的对象,并不具有prototype属性,其默认有一个__proto__属性,__proto__的值指向构造函数的原型对象。在原型对象上添加或修改的属性,在所有实例化出的对象上都可共享。
当在实例化的对象中访问一个属性时,首先会在该对象内部寻找,如找不到,则会向其__proto__指向的原型中寻找,如仍找不到,则继续向原型中__proto__指向的上级原型中寻找,直至找到或Object.prototype为止,这种链状过程即为原型链。
44.何为防抖和节流?如何实现?
防抖和节流都是防止短时间内高频触发事件的方案。
防抖的原理是:如果一定时间内多次执行了某事件,则只执行其中的最后一次。
节流的原理是:要执行的事件每隔一段时间会被冷却,无法执行。
应用场景有:搜索框实时搜索,滚动改变相关的事件。
//fn: 要执行的函数
//delay: 设定的时限
//防抖函数
function debunce(fn,delay){
let flag = null;
return function(){
if(flag) clearTimeout(flag)
//利用apply改变函数指向,使得封装后的函数可以接收event本身
flag = setTimeout(()=>fn.apply(this,arguments),delay)
}
}
//节流函数
function throttle(fn,delay){
let flag = true;
return function(){
if(!flag) return false;
flag = false;
setTimeout(()=>{
fn.apply(this,arguments)
flag=true
},delay)
}
}
45.如何理解同步和异步?
同步:按照代码书写顺序一一执行处理指令的一种模式,上一段代码执行完才能执行下一段代码。
异步:可以理解为一种并行处理的方式,不必等待一个程序执行完,可以执行其它的任务。
46.JS是如何实现异步的?
JS引擎是单线程的,但又能实现异步的原因在于事件循环和任务队列体系。
- 事件循环:
- JS 会创建一个类似于 while (true) 的循环,每执行一次循环体的过程称之为 Tick。每次 Tick 的过程就是查看是否有待处理事件,如果有则取出相关事件及回调函数放入执行栈中由主线程执行。待处理的事件会存储在一个任务队列中,也就是每次 Tick 会查看任务队列中是否有需要执行的任务。
- 任务队列:
- 异步操作会将相关回调添加到任务队列中。而不同的异步操作添加到任务队列的时机也不同,如 onclick, setTimeout, ajax 处理的方式都不同,这些异步操作是由浏览器内核的 webcore 来执行的,浏览器内核包含3种 webAPI,分别是 DOM Binding、network、timer模块。
onclick 由 DOM Binding 模块来处理,当事件触发的时候,回调函数会立即添加到任务队列中。
setTimeout 由 timer 模块来进行延时处理,当时间到达的时候,才会将回调函数添加到任务队列中。
ajax 由network 模块来处理,在网络请求完成返回之后,才将回调添加到任务队列中。
- 异步操作会将相关回调添加到任务队列中。而不同的异步操作添加到任务队列的时机也不同,如 onclick, setTimeout, ajax 处理的方式都不同,这些异步操作是由浏览器内核的 webcore 来执行的,浏览器内核包含3种 webAPI,分别是 DOM Binding、network、timer模块。
- 主线程:
- JS 只有一个线程,称之为主线程。而事件循环是主线程中执行栈里的代码执行完毕之后,才开始执行的。所以,主线程中要执行的代码时间过长,会阻塞事件循环的执行,也就会阻塞异步操作的执行。
只有当主线程中执行栈为空的时候(即同步代码执行完后),才会进行事件循环来观察要执行的事件回调,当事件循环检测到任务队列中有事件就取出相关回调放入执行栈中由主线程执行。
- JS 只有一个线程,称之为主线程。而事件循环是主线程中执行栈里的代码执行完毕之后,才开始执行的。所以,主线程中要执行的代码时间过长,会阻塞事件循环的执行,也就会阻塞异步操作的执行。
47.什么是AJAX?如何实现?
ajax是一种能够实现局部网页刷新的技术,可以使网页异步刷新。
ajax的实现主要包括四个步骤:
(1)创建核心对象XMLhttpRequest;
(2)利用open方法打开与服务器的连接;
(3)利用send方法发送请求;("POST"请求时,还需额外设置请求头)
(4)监听服务器响应,接收返回值。
//1-创建核心对象
//该对象有兼容问题,低版本浏览器应使用ActiveXObject
const xthhp = new XMLHttpRequest();
//2-连接服务器
//open(method,url,async)
xhttp.open("POST","http://localhost:3000",true)
//设置请求头
xmlHttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
//3-发送请求
//send方法发送请求参数,如为GET方法,则在open中url后拼接
xhttp.send({_id:123})
//4-接收服务器响应
//onreadystatechange事件,会在xhttp的状态发生变化时自动调用
xhttp.onreadystatechange =function(){
//状态码共5种:0-未open 1-已open 2-已send 3-读取响应 4-响应读取结束
if(xhttp.readyState == 4 && xhttp.status == 200){
alert("ajax请求已完成")
}
}
48.实现异步的方式有哪些?
- 回调函数模式:将需要异步执行的函数作为回调函数执行,其缺点在于处理复杂逻辑异步逻辑时,会造成回调地狱(回调嵌套层数太多,代码结构混乱);
- 事件监听模式:采用事件驱动的思想,当某一事件发生时触发执行异步函数,其缺点在于整个代码全部得变为事件驱动模式,难以分辨主流程;
- 发布订阅模式:当异步任务执行完成时发布消息给信号中心,其他任务通过在信号中心中订阅消息来确定自己是否开始执行;
- Promise(ES6):Promise对象共有三种状态pending(初始化状态)、fulfilled(成功状态)、rejected(失败状态)。
- async/await(ES7):基于Promise实现的异步函数;
- 利用生成器实现。
49.怎么理解Promise对象?
- Promise对象有如下两个特点:
- 对象的状态不受外界影响。Promise对象共有三种状态pending、fulfilled、rejected。状态值只会被异步结果决定,其他任何操作无法改变。
- 状态一旦成型,就不会再变,且任何时候都可得到这个结果。状态值会由pending变为fulfilled或rejected,这时即为resolved。
- Promise的缺点有如下三个缺点:
- Promise一旦执行便无法被取消;
- 不可设置回调函数,其内部发生的错误无法捕获;
- 当处于pending状态时,无法得知其具体发展到了哪个阶段。
- Pomise中常用的方法有:
- Promise.prototype.then():Promise实例的状态发生改变时,会调用then内部的回调函数。then方法接受两个参数(第一个为resolved状态时时执行的回调,第一个为rejected状态时时执行的回调)
- Promise.prototype.catch():.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数。
50.怎么理解宏任务,微任务???
宏任务有:script(整体代码)、setTimeout、setInterval、I/O、页面渲染;
微任务有:Promise.then、Object.observe、MutationObserver。
执行顺序大致如下:
主线程任务——>宏任务——>微任务——>微任务里的宏任务——>…——>直到任务全部完成
51.什么是跨域?怎么解决跨域问题?
跨域问题实际是由同源策略衍生出的一个问题,当传输协议、域名、端口任一部分不一致时,便会产生跨域问题,从而拒绝请求,但
- JSONP
- 原理:利用script标签没有跨域限制的漏洞,使得网页可以得到从其他来源动态产生的JSON数据(前提是服务器支持)。
- 优点:实现简单,兼容性好。
- 缺点:仅支持get方法,容易受到XSS攻击。
- 原理:利用script标签没有跨域限制的漏洞,使得网页可以得到从其他来源动态产生的JSON数据(前提是服务器支持)。
- CORS
- 原理:服务器端设置Access-Control-Allow-Origin以开启CORS。该属性表示哪些域名可以访问资源,如设置通配符则表示所有网站均可访问。
实现实例(express):
//app.js中设置
var app = express();
//CORS跨域-------------------------------------------------------------------------------------
// CORS:设置允许跨域中间件
var allowCrossDomain = function (req, res, next) {
// 设置允许跨域访问的 URL(* 表示允许任意 URL 访问)
res.header("Access-Control-Allow-Origin", "*");
// 设置允许跨域访问的请求头
res.header("Access-Control-Allow-Headers", "X-Requested-With,Origin,Content-Type,Accept,Authorization");
// 设置允许跨域访问的请求类型
res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS");
// 设置允许服务器接收 cookie
res.header('Access-Control-Allow-Credentials', 'true');
next();
};
app.use(allowCrossDomain);
//------------------------------------------------------------------------------------
- Node中间件代理
- 原理:同源策略仅是浏览器需要遵循的策略,故搭建中间件服务器转发请求与响应,达到跨域目的。
/* server1.js 代理服务器(http://localhost:3000)*/
const http = require('http')
// 第一步:接受客户端请求
const server = http.createServer((request, response) => {
// 代理服务器,直接和浏览器直接交互,需要设置CORS 的首部字段
response.writeHead(200,{
'Access-Control-Allow-Origin':'*',
'Access-Control-Allow-Methods':'*',
'Access-Control-Allow-Headers':'Content-Type'
})
// 第二步:将请求转发给服务器
const proxyRequest = http.request({
host:'127.0.0.1',
port:4000,
url:'/',
method:request.method,
headers:request.headers
},
serverResponse =>{
// 第三步:收到服务器的响应
var body = ''
serverResponse.on('data', chunk =>{
body += chunk
})
serverResponse.on('end',()=> {
console.log('The data is '+ body)
// 第四步:将响应结果转发给浏览器
response.end(body)
})
})
.end()
})
server.listen(3000,()=>{console.log('中间件服务器地址: http://localhost:3000')})
// server2.js(http://localhost:4000)
const http = require("http");
const data = { title: "fontend", password: "123456" };
const server = http.createServer((request, response) => {
if (request.url === "/") {
response.end(JSON.stringify(data));
}
});
server.listen(4000, () => {
console.log("The server is running at http://localhost:4000");
});
- nginx反向代理
- 原理:类似Node中间件服务器,通过nginx代理服务器实现。
实现方法:下载安装nginx,修改配置。
- 原理:类似Node中间件服务器,通过nginx代理服务器实现。
52.DOM事件模型和事件流?
DOM事件模型包括事件捕获(自上而下触发)与事件冒泡(自下而上触发,ie用的就是冒泡)机制。基于事件冒泡机制可以完成事件代理。
- 事件捕获
- 事件冒泡
DOM事件流包括三个阶段事件捕获阶段、处于目标阶段、事件冒泡阶段。
53.EventLoop事件循环是什么?
- js是一门单线程的需要,它的异步操作都是通过事件循环来完成的。整个事件循环大体由执行栈、消息队列和微任务队列三个部分组成。
- 同步代码会直接在执行栈中调用执行。
- 定时器中的回调会在执行栈被清空且定时达成时推入执行栈中执行。
- promise、async异步函数的回调会被推入到微任务队列中,当执行栈被清空且异步操作完成时立即执行。
- 执行栈在执行完同步任务后,查看执行栈是否为空,如果执行栈为空,就会去检查微任务(microTask)队列是否为空,如果为空的话,就执行Task(宏任务),否则就一次性执行完所有微任务。每次单个宏任务执行完毕后,检查微任务(microTask)队列是否为空,如果不为空的话,会按照先入先出的规则全部执行完微任务(microTask)后,设置微任务(microTask)队列为null,然后再执行宏任务,如此循环。
54.require/import之间的区别?
- require是CommonJS语法,import是ES6语法;
- require只在后端服务器支持,import在高版本浏览器及Node中都可以支持;
- require引入的是原始导出值的复制,import则是导出值的引用;
- require时运行时动态加载,import是静态编译;
- require调用时默认不是严格模式,import则默认调用严格模式.
55.new操作符都做了些什么,手写实现new
- 创建一个简单的空对象
- 链接该对象(即设置该对象的构造函数)到另一个对象 ;
- 将创建的新的对象作为this的上下文
- 如果该函数没有返回对象则返回this, 如果返回值是一个对象就返回该对象,否则返回构造函数的一个实例对象
function create(){
var obj = new Object();
let con = [].shift.call(arguments);
obj.__proto__ = con.prototype;
con.apply(obj, arguments);
return obj;
}
function Car(color){
this.color = color;
}
Car.prototype.start = function() {
console.log(this.color + "zhaobuchu");
}
var car = create(Car, "blue")
console.log(car.color);
console.log(car.start());
56.js中的数据类型
基本类型:number string boolean null undefined symbol
引用数据类型:object (包含:Date,RegExp,Function,Array,Math…)
57.requestAnimationFrame的优势
requestAnimationFrame不需要指定间隔时间,它采用的是系统间隔,一般是1秒60帧,每个16ms刷新一次。 好处:
- 将更新在一次回流中全部提交,提升性能
- 当页面处于未激活状态时,requestAnimationFrame也会停止渲染,当再次激活时,就会接着上一部分继续执行
58.箭头函数
箭头函数有几个使用注意点。
- 函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
- 不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
- 不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
- 不可以使用yield命令,因此箭头函数不能用作 Generator 函数。
59.curry
function mycurry(fn, beforeRoundArg = []) {
return function () {
let args = [...beforeRoundArg, ...arguments]
if (args.length < fn.length) {
return mycurry.call(this, fn, args)
} else {
return fn.apply(this, args)
}
}
}
function sum(a, b, c) {
return a + b + c
}
let sumFn = mycurry(sum)
console.log(sumFn(1)(2)(3))//6
60.数组扁平化
数组扁平化是指将一个多维数组变为一维数组
[1, [2, 3, [4, 5]]] ------> [1, 2, 3, 4, 5]
- reduce
遍历数组每一项,若值为数组则递归遍历,否则concat。
reduce是数组的一种方法,它接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。
reduce包含两个参数:回调函数,传给total的初始值
function flatten(arr) {
return arr.reduce((result, item)=> {
return result.concat(Array.isArray(item) ? flatten(item) : item);
}, []);
}
- toString & split
调用数组的toString方法,将数组变为字符串然后再用split分割还原为数组
因为split分割后形成的数组的每一项值为字符串,所以需要用一个map方法遍历数组将其每一项转换为数值型
function flatten(arr) {
return arr.toString().split(',').map(function(item) {
return Number(item);
})
}
- join & split
和上面的toString一样,join也可以将数组转换为字符串
function flatten(arr) {
return arr.join(',').split(',').map(function(item) {
return parseInt(item);
})
}
- 递归
递归的遍历每一项,若为数组则继续遍历,否则concat
function flatten(arr) {
var res = [];
arr.map(item => {
if(Array.isArray(item)) {
res = res.concat(flatten(item));
} else {
res.push(item);
}
});
return res;
}
- 扩展运算符
es6的扩展运算符能将二维数组变为一维
[].concat(...[1, 2, 3, [4, 5]]); // [1, 2, 3, 4, 5]
根据这个结果我们可以做一个遍历,若arr中含有数组则使用一次扩展运算符,直至没有为止。
function flatten(arr) {
while(arr.some(item=>Array.isArray(item))) {
arr = [].concat(...arr);
}
return arr;
}
61.数组去重
- 利用ES6 Set去重(ES6中最常用)
function unique (arr) {
return Array.from(new Set(arr))
}
var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
console.log(unique(arr))
//[1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {}, {}]
- Map方法
let arr1 = [5, 5, 9, 9, 6, 3, 5, 4, 8, 6, 4, 5];
function unique(arr){
let map = new Map();
let array = new Array(); // 数组用于返回结果
for (let i = 0; i < arr.length; i++) {
if(map.has(arr[i])) { // 判断 map 中是否已有该 key 值
map.set(arr[i], true); // 后面的true 代表该 key 值在原始数组中重复了,false反之
} else { // 如果 map 中没有该 key 值,添加
map.set(arr[i], false);
array.push(arr[i]);
}
}
return array;
}
unique(arr1);
- indexOf方法
let arr1 = [5, 5, 9, 9, 6, 3, 5, 4, 8, 6, 4, 5];
function unique(arr) {
var arr2 = [];
for (let i = 0; i < arr.length; i++) {
if (arr2.indexOf(arr[i]) === -1) {
arr2.push(arr[i]);
}
}
return arr2;
}
unique(arr1)
//简化
let arr1 = [5, 5, 9, 9, 6, 3, 5, 4, 8, 6, 4, 5];
function unique(arr) {
var arr2 = [];
for (let i = 0; i < arr.length; i++) {
arr2.indexOf(arr[i]) === -1 ? arr2.push(arr[i]) : [];
}
return arr2;
}
unique(arr1);
- 利用双for循环,然后splice去重
let arr1 = [5, 5, 9, 9, 6, 3, 5, 4, 8, 6, 4, 5];
function unique(arr){
for(var i=0; i<arr.length; i++){
for(var j=i+1; j<arr.length; j++){
if(arr[i]==arr[j]){ //第一个等同于第二个,splice方法删除第二个
arr.splice(j,1);
j--;
}
}
}
return arr;
}
unique(arr1)
- sort()方法
let arr1 = [5, 5, 9, 9, 6, 3, 5, 4, 8, 6, 4, 5];
function unique(arr) {
arr = arr.sort()
var arrry= [arr[0]];
for (var i = 1; i < arr.length; i++) {
if (arr[i] !== arr[i-1]) {
arrry.push(arr[i]);
}
}
return arrry;
}
unique(arr1)