likes
comments
collection
share

滴滴前端二面面试题

作者站长头像
站长
· 阅读数 23

HTTP请求报文的是什么样的?

请求报⽂有4部分组成:

  • 请求⾏
  • 请求头部
  • 空⾏
  • 请求体

    其中: (1)请求⾏包括:请求⽅法字段、URL字段、HTTP协议版本字段。它们⽤空格分隔。例如,GET /index.html HTTP/1.1。(2)请求头部:请求头部由关键字/值对组成,每⾏⼀对,关键字和值⽤英⽂冒号“:”分隔

  • User-Agent:产⽣请求的浏览器类型。
  • Accept:客户端可识别的内容类型列表。
  • Host:请求的主机名,允许多个域名同处⼀个IP地址,即虚拟主机。

(3)请求体: post put等请求携带的数据

JSONP

JSONP 核心原理:script 标签不受同源策略约束,所以可以用来进行跨域请求,优点是兼容性好,但是只能用于 GET 请求;

const jsonp = ({ url, params, callbackName }) => {
    const generateUrl = () => {
        let dataSrc = ''
        for (let key in params) {
            if (params.hasOwnProperty(key)) {
                dataSrc += `${key}=${params[key]}&`
            }
        }
        dataSrc += `callback=${callbackName}`
        return `${url}?${dataSrc}`
    }
    return new Promise((resolve, reject) => {
        const scriptEle = document.createElement('script')
        scriptEle.src = generateUrl()
        document.body.appendChild(scriptEle)
        window[callbackName] = data => {
            resolve(data)
            document.removeChild(scriptEle)
        }
    })
}

事件传播机制(事件流)

冒泡和捕获

什么是HTTPS协议?

超文本传输安全协议(Hypertext Transfer Protocol Secure,简称:HTTPS)是一种通过计算机网络进行安全通信的传输协议。HTTPS经由HTTP进行通信,利用SSL/TLS来加密数据包。HTTPS的主要目的是提供对网站服务器的身份认证,保护交换数据的隐私与完整性。 HTTP协议采用明文传输信息,存在信息窃听信息篡改信息劫持的风险,而协议TLS/SSL具有身份验证信息加密完整性校验的功能,可以避免此类问题发生。

安全层的主要职责就是对发起的HTTP请求的数据进行加密操作对接收到的HTTP的内容进行解密操作

伪元素和伪类的区别和作用?

  • 伪元素:在内容元素的前后插入额外的元素或样式,但是这些元素实际上并不在文档中生成。它们只在外部显示可见,但不会在文档的源代码中找到它们,因此,称为“伪”元素。例如:
p::before {content:"第一章:";}
p::after {content:"Hot!";}
p::first-line {background:red;}
p::first-letter {font-size:30px;}
  • 伪类:将特殊的效果添加到特定选择器上。它是已有元素上添加类别的,不会产生新的元素。例如:
a:hover {color: #FF00FF}
p:first-child {color: red}

总结: 伪类是通过在元素选择器上加⼊伪类改变元素状态,⽽伪元素通过对元素的操作进⾏对元素的改变。

setTimeout 模拟 setInterval

描述:使用setTimeout模拟实现setInterval的功能。

实现

const mySetInterval(fn, time) {
    let timer = null;
    const interval = () => {
        timer = setTimeout(() => {
            fn();  // time 时间之后会执行真正的函数fn
            interval();  // 同时再次调用interval本身
        }, time)
    }
    interval();  // 开始执行
    // 返回用于关闭定时器的函数
    return () => clearTimeout(timer);
}

// 测试
const cancel = mySetInterval(() => console.log(1), 400);
setTimeout(() => {
    cancel();
}, 1000);  
// 打印两次1

行内元素有哪些?块级元素有哪些? 空(void)元素有那些?

  • 行内元素有:a b span img input select strong
  • 块级元素有:div ul ol li dl dt dd h1 h2 h3 h4 h5 h6 p

空元素,即没有内容的HTML元素。空元素是在开始标签中关闭的,也就是空元素没有闭合标签:

  • 常见的有:<br><hr><img><input><link><meta>
  • 鲜见的有:<area><base><col><colgroup><command><embed><keygen><param><source><track><wbr>

说一下你对盒模型的理解?

CSS3中的盒模型有以下两种:标准盒模型、IE盒模型
盒模型都是由四个部分组成的,分别是margin、border、padding和content
标准盒模型和IE盒模型的区别在于设置width和height时, 所对应的范围不同
1、标准盒模型的width和height属性的范围只包含了content
2、IE盒模型的width和height属性的范围包含了border、padding和content
可以通过修改元素的box-sizing属性来改变元素的盒模型;
1、box-sizing:content-box表示标准盒模型(默认值)
2、box-sizing:border-box表示IE盒模型(怪异盒模型)

发布订阅模式

题目描述:实现一个发布订阅模式拥有 on emit once off 方法

实现代码如下:

class EventEmitter {
  constructor() {
    this.events = {};
  }
  // 实现订阅
  on(type, callBack) {
    if (!this.events[type]) {
      this.events[type] = [callBack];
    } else {
      this.events[type].push(callBack);
    }
  }
  // 删除订阅
  off(type, callBack) {
    if (!this.events[type]) return;
    this.events[type] = this.events[type].filter((item) => {
      return item !== callBack;
    });
  }
  // 只执行一次订阅事件
  once(type, callBack) {
    function fn() {
      callBack();
      this.off(type, fn);
    }
    this.on(type, fn);
  }
  // 触发事件
  emit(type, ...rest) {
    this.events[type] &&
      this.events[type].forEach((fn) => fn.apply(this, rest));
  }
}
// 使用如下
// const event = new EventEmitter();

// const handle = (...rest) => {
//   console.log(rest);
// };

// event.on("click", handle);

// event.emit("click", 1, 2, 3, 4);

// event.off("click", handle);

// event.emit("click", 1, 2);

// event.once("dbClick", () => {
//   console.log(123456);
// });
// event.emit("dbClick");
// event.emit("dbClick");

闭包的应用场景

  • 柯里化 bind
  • 模块

let 闭包

let 会产生临时性死区,在当前的执行上下文中,会进行变量提升,但是未被初始化,所以在执行上下文执行阶段,执行代码如果还没有执行到变量赋值,就引用此变量就会报错,此变量未初始化。

TCP的流量控制机制

一般来说,流量控制就是为了让发送方发送数据的速度不要太快,要让接收方来得及接收。TCP采用大小可变的滑动窗口进行流量控制,窗口大小的单位是字节。这里说的窗口大小其实就是每次传输的数据大小。

  • 当一个连接建立时,连接的每一端分配一个缓冲区来保存输入的数据,并将缓冲区的大小发送给另一端。
  • 当数据到达时,接收方发送确认,其中包含了自己剩余的缓冲区大小。(剩余的缓冲区空间的大小被称为窗口,指出窗口大小的通知称为窗口通告 。接收方在发送的每一确认中都含有一个窗口通告。)
  • 如果接收方应用程序读数据的速度能够与数据到达的速度一样快,接收方将在每一确认中发送一个正的窗口通告。
  • 如果发送方操作的速度快于接收方,接收到的数据最终将充满接收方的缓冲区,导致接收方通告一个零窗口 。发送方收到一个零窗口通告时,必须停止发送,直到接收方重新通告一个正的窗口。

对this对象的理解

this 是执行上下文中的一个属性,它指向最后一次调用这个方法的对象。在实际开发中,this 的指向可以通过四种调用模式来判断。

  • 第一种是函数调用模式,当一个函数不是一个对象的属性时,直接作为函数来调用时,this 指向全局对象。
  • 第二种是方法调用模式,如果一个函数作为一个对象的方法来调用时,this 指向这个对象。
  • 第三种是构造器调用模式,如果一个函数用 new 调用时,函数执行前会新创建一个对象,this 指向这个新创建的对象。
  • 第四种是 apply 、 call 和 bind 调用模式,这三个方法都可以显示的指定调用函数的 this 指向。其中 apply 方法接收两个参数:一个是 this 绑定的对象,一个是参数数组。call 方法接收的参数,第一个是 this 绑定的对象,后面的其余参数是传入函数执行的参数。也就是说,在使用 call() 方法时,传递给函数的参数必须逐个列举出来。bind 方法通过传入一个对象,返回一个 this 绑定了传入对象的新函数。这个函数的 this 指向除了使用 new 时会被改变,其他情况下都不会改变。

这四种方式,使用构造器调用模式的优先级最高,然后是 apply、call 和 bind 调用模式,然后是方法调用模式,然后是函数调用模式。

代码输出结果

function SuperType(){
    this.property = true;
}

SuperType.prototype.getSuperValue = function(){
    return this.property;
};

function SubType(){
    this.subproperty = false;
}

SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function (){
    return this.subproperty;
};

var instance = new SubType();
console.log(instance.getSuperValue());

输出结果:true

实际上,这段代码就是在实现原型链继承,SubType继承了SuperType,本质是重写了SubType的原型对象,代之以一个新类型的实例。SubType的原型被重写了,所以instance.constructor指向的是SuperType。具体如下:

什么是 DOM 和 BOM?

  • DOM 指的是文档对象模型,它指的是把文档当做一个对象,这个对象主要定义了处理网页内容的方法和接口。
  • BOM 指的是浏览器对象模型,它指的是把浏览器当做一个对象来对待,这个对象主要定义了与浏览器进行交互的法和接口。BOM的核心是 window,而 window 对象具有双重角色,它既是通过 js 访问浏览器窗口的一个接口,又是一个 Global(全局)对象。这意味着在网页中定义的任何对象,变量和函数,都作为全局对象的一个属性或者方法存在。window 对象含有 location 对象、navigator 对象、screen 对象等子对象,并且 DOM 的最根本的对象 document 对象也是 BOM 的 window 对象的子对象。

懒加载的实现原理

图片的加载是由src引起的,当对src赋值时,浏览器就会请求图片资源。根据这个原理,我们使用HTML5 的data-xxx属性来储存图片的路径,在需要加载图片的时候,将data-xxx中图片的路径赋值给src,这样就实现了图片的按需加载,即懒加载。

注意:data-xxx 中的xxx可以自定义,这里我们使用data-src来定义。

懒加载的实现重点在于确定用户需要加载哪张图片,在浏览器中,可视区域内的资源就是用户需要的资源。所以当图片出现在可视区域时,获取图片的真实地址并赋值给图片即可。

使用原生JavaScript实现懒加载:

知识点:

(1)window.innerHeight 是浏览器可视区的高度

(2)document.body.scrollTop || document.documentElement.scrollTop 是浏览器滚动的过的距离

(3)imgs.offsetTop 是元素顶部距离文档顶部的高度(包括滚动条的距离)

(4)图片加载条件:img.offsetTop < window.innerHeight + document.body.scrollTop;

图示: 代码实现:

<div class="container">
     <img src="loading.gif"  data-src="pic.png">
     <img src="loading.gif"  data-src="pic.png">
     <img src="loading.gif"  data-src="pic.png">
     <img src="loading.gif"  data-src="pic.png">
     <img src="loading.gif"  data-src="pic.png">
     <img src="loading.gif"  data-src="pic.png">
</div>
<script>
var imgs = document.querySelectorAll('img');
function lozyLoad(){
        var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
        var winHeight= window.innerHeight;
        for(var i=0;i < imgs.length;i++){
            if(imgs[i].offsetTop < scrollTop + winHeight ){
                imgs[i].src = imgs[i].getAttribute('data-src');
            }
        }
    }
  window.onscroll = lozyLoad();
</script>

如何对项目中的图片进行优化?

  1. 不用图片。很多时候会使用到很多修饰类图片,其实这类修饰图片完全可以用 CSS 去代替。
  2. 对于移动端来说,屏幕宽度就那么点,完全没有必要去加载原图浪费带宽。一般图片都用 CDN 加载,可以计算出适配屏幕的宽度,然后去请求相应裁剪好的图片。
  3. 小图使用 base64 格式
  4. 将多个图标文件整合到一张图片中(雪碧图)
  5. 选择正确的图片格式:

    • 对于能够显示 WebP 格式的浏览器尽量使用 WebP 格式。因为 WebP 格式具有更好的图像数据压缩算法,能带来更小的图片体积,而且拥有肉眼识别无差异的图像质量,缺点就是兼容性并不好
    • 小图使用 PNG,其实对于大部分图标这类图片,完全可以使用 SVG 代替
    • 照片使用 JPEG

(1) IE 浏览器内核:Trident 内核,也是俗称的 IE 内核;

(2) Chrome 浏览器内核:统称为 Chromium 内核或 Chrome 内核,以前是 Webkit 内核,现在是 Blink内核;

(3) Firefox 浏览器内核:Gecko 内核,俗称 Firefox 内核;

(4) Safari 浏览器内核:Webkit 内核;

(5) Opera 浏览器内核:最初是自己的 Presto 内核,后来加入谷歌大军,从 Webkit 又到了 Blink 内核;

(6) 360浏览器、猎豹浏览器内核:IE + Chrome 双内核;

(7) 搜狗、遨游、QQ 浏览器内核:Trident(兼容模式)+ Webkit(高速模式);

(8) 百度浏览器、世界之窗内核:IE 内核;

(9) 2345浏览器内核:好像以前是 IE 内核,现在也是 IE + Chrome 双内核了;

(10)UC 浏览器内核:这个众口不一,UC 说是他们自己研发的 U3 内核,但好像还是基于 Webkit 和 Trident ,还有说是基于火狐内核。

如何提⾼webpack的打包速度?

(1)优化 Loader

对于 Loader 来说,影响打包效率首当其冲必属 Babel 了。因为 Babel 会将代码转为字符串生成 AST,然后对 AST 继续进行转变最后再生成新的代码,项目越大,转换代码越多,效率就越低。当然了,这是可以优化的。

首先我们优化 Loader 的文件搜索范围

module.exports = {
  module: {
    rules: [
      {
        // js 文件才使用 babel
        test: /\.js$/,
        loader: 'babel-loader',
        // 只在 src 文件夹下查找
        include: [resolve('src')],
        // 不会去查找的路径
        exclude: /node_modules/
      }
    ]
  }
}

对于 Babel 来说,希望只作用在 JS 代码上的,然后 node_modules 中使用的代码都是编译过的,所以完全没有必要再去处理一遍。

当然这样做还不够,还可以将 Babel 编译过的文件缓存起来,下次只需要编译更改过的代码文件即可,这样可以大幅度加快打包时间

loader: 'babel-loader?cacheDirectory=true'

(2)HappyPack

受限于 Node 是单线程运行的,所以 Webpack 在打包的过程中也是单线程的,特别是在执行 Loader 的时候,长时间编译的任务很多,这样就会导致等待的情况。

HappyPack 可以将 Loader 的同步执行转换为并行的,这样就能充分利用系统资源来加快打包效率了

module: {
  loaders: [
    {
      test: /\.js$/,
      include: [resolve('src')],
      exclude: /node_modules/,
      // id 后面的内容对应下面
      loader: 'happypack/loader?id=happybabel'
    }
  ]
},
plugins: [
  new HappyPack({
    id: 'happybabel',
    loaders: ['babel-loader?cacheDirectory'],
    // 开启 4 个线程
    threads: 4
  })
]

(3)DllPlugin

DllPlugin 可以将特定的类库提前打包然后引入。这种方式可以极大的减少打包类库的次数,只有当类库更新版本才有需要重新打包,并且也实现了将公共代码抽离成单独文件的优化方案。DllPlugin的使用方法如下:

// 单独配置在一个文件中
// webpack.dll.conf.js
const path = require('path')
const webpack = require('webpack')
module.exports = {
  entry: {
    // 想统一打包的类库
    vendor: ['react']
  },
  output: {
    path: path.join(__dirname, 'dist'),
    filename: '[name].dll.js',
    library: '[name]-[hash]'
  },
  plugins: [
    new webpack.DllPlugin({
      // name 必须和 output.library 一致
      name: '[name]-[hash]',
      // 该属性需要与 DllReferencePlugin 中一致
      context: __dirname,
      path: path.join(__dirname, 'dist', '[name]-manifest.json')
    })
  ]
}

然后需要执行这个配置文件生成依赖文件,接下来需要使用 DllReferencePlugin 将依赖文件引入项目中

// webpack.conf.js
module.exports = {
  // ...省略其他配置
  plugins: [
    new webpack.DllReferencePlugin({
      context: __dirname,
      // manifest 就是之前打包出来的 json 文件
      manifest: require('./dist/vendor-manifest.json'),
    })
  ]
}

(4)代码压缩

在 Webpack3 中,一般使用 UglifyJS 来压缩代码,但是这个是单线程运行的,为了加快效率,可以使用 webpack-parallel-uglify-plugin 来并行运行 UglifyJS,从而提高效率。

在 Webpack4 中,不需要以上这些操作了,只需要将 mode 设置为 production 就可以默认开启以上功能。代码压缩也是我们必做的性能优化方案,当然我们不止可以压缩 JS 代码,还可以压缩 HTML、CSS 代码,并且在压缩 JS 代码的过程中,我们还可以通过配置实现比如删除 console.log 这类代码的功能。

(5)其他

可以通过一些小的优化点来加快打包速度

  • resolve.extensions:用来表明文件后缀列表,默认查找顺序是 ['.js', '.json'],如果你的导入文件没有添加后缀就会按照这个顺序查找文件。我们应该尽可能减少后缀列表长度,然后将出现频率高的后缀排在前面
  • resolve.alias:可以通过别名的方式来映射一个路径,能让 Webpack 更快找到路径
  • module.noParse:如果你确定一个文件下没有其他依赖,就可以使用该属性让 Webpack 不扫描该文件,这种方式对于大型的类库很有帮助

与缓存相关的HTTP请求头有哪些

强缓存:

  • Expires
  • Cache-Control

协商缓存:

  • Etag、If-None-Match
  • Last-Modified、If-Modified-Since

JavaScript有哪些内置对象

全局的对象( global objects )或称标准内置对象,不要和 "全局对象(global object)" 混淆。这里说的全局的对象是说在全局作用域里的对象。全局作用域中的其他对象可以由用户的脚本创建或由宿主程序提供。

标准内置对象的分类:

(1)值属性,这些全局属性返回一个简单值,这些值没有自己的属性和方法。例如 Infinity、NaN、undefined、null 字面量

(2)函数属性,全局函数可以直接调用,不需要在调用时指定所属对象,执行结束后会将结果直接返回给调用者。例如 eval()、parseFloat()、parseInt() 等

(3)基本对象,基本对象是定义或使用其他对象的基础。基本对象包括一般对象、函数对象和错误对象。例如 Object、Function、Boolean、Symbol、Error 等

(4)数字和日期对象,用来表示数字、日期和执行数学计算的对象。例如 Number、Math、Date

(5)字符串,用来表示和操作字符串的对象。例如 String、RegExp

(6)可索引的集合对象,这些对象表示按照索引值来排序的数据集合,包括数组和类型数组,以及类数组结构的对象。例如 Array

(7)使用键的集合对象,这些集合对象在存储数据时会使用到键,支持按照插入顺序来迭代元素。例如 Map、Set、WeakMap、WeakSet

(8)矢量集合,SIMD 矢量集合中的数据会被组织为一个数据序列。例如 SIMD 等

(9)结构化数据,这些对象用来表示和操作结构化的缓冲区数据,或使用 JSON 编码的数据。例如 JSON 等

(10)控制抽象对象例如 Promise、Generator 等

(11)反射。例如 Reflect、Proxy

(12)国际化,为了支持多语言处理而加入 ECMAScript 的对象。例如 Intl、Intl.Collator 等

(13)WebAssembly

(14)其他。例如 arguments

总结: js 中的内置对象主要指的是在程序执行前存在全局作用域里的由 js 定义的一些全局值属性、函数和用来实例化其他对象的构造函数对象。一般经常用到的如全局变量值 NaN、undefined,全局函数如 parseInt()、parseFloat() 用来实例化对象的构造函数如 Date、Object 等,还有提供数学计算的单体内置对象如 Math 对象。

函数柯里化

什么叫函数柯里化?其实就是将使用多个参数的函数转换成一系列使用一个参数的函数的技术。还不懂?来举个例子。

function add(a, b, c) {
    return a + b + c
}
add(1, 2, 3)
let addCurry = curry(add)
addCurry(1)(2)(3)

现在就是要实现 curry 这个函数,使函数从一次调用传入多个参数变成多次调用每次传一个参数。

function curry(fn) {
    let judge = (...args) => {
        if (args.length == fn.length) return fn(...args)
        return (...arg) => judge(...args, ...arg)
    }
    return judge
}

手写发布订阅

class EventListener {
    listeners = {};
    on(name, fn) {
        (this.listeners[name] || (this.listeners[name] = [])).push(fn)
    }
    once(name, fn) {
        let tem = (...args) => {
            this.removeListener(name, fn)
            fn(...args)
        }
        fn.fn = tem
        this.on(name, tem)
    }
    removeListener(name, fn) {
        if (this.listeners[name]) {
            this.listeners[name] = this.listeners[name].filter(listener => (listener != fn && listener != fn.fn))
        }
    }
    removeAllListeners(name) {
        if (name && this.listeners[name]) delete this.listeners[name]
        this.listeners = {}
    }
    emit(name, ...args) {
        if (this.listeners[name]) {
            this.listeners[name].forEach(fn => fn.call(this, ...args))
        }
    }
}

文档声明(Doctype)和<!Doctype html>有何作用? 严格模式与混杂模式如何区分?它们有何意义?

文档声明的作用: 文档声明是为了告诉浏览器,当前HTML文档使用什么版本的HTML来写的,这样浏览器才能按照声明的版本来正确的解析。

的作用:<!doctype html> 的作用就是让浏览器进入标准模式,使用最新的 HTML5 标准来解析渲染页面;如果不写,浏览器就会进入混杂模式,我们需要避免此类情况发生。

严格模式与混杂模式的区分:

  • 严格模式: 又称为标准模式,指浏览器按照W3C标准解析代码;
  • 混杂模式: 又称怪异模式、兼容模式,是指浏览器用自己的方式解析代码。混杂模式通常模拟老式浏览器的行为,以防止老站点无法工作;

区分:网页中的DTD,直接影响到使用的是严格模式还是浏览模式,可以说DTD的使用与这两种方式的区别息息相关。

  • 如果文档包含严格的DOCTYPE ,那么它一般以严格模式呈现(严格 DTD ——严格模式);
  • 包含过渡 DTDURIDOCTYPE ,也以严格模式呈现,但有过渡 DTD 而没有 URI (统一资源标识符,就是声明最后的地址)会导致页面以混杂模式呈现(有 URI 的过渡 DTD ——严格模式;没有 URI 的过渡 DTD ——混杂模式);
  • DOCTYPE 不存在或形式不正确会导致文档以混杂模式呈现(DTD不存在或者格式不正确——混杂模式);
  • HTML5 没有 DTD ,因此也就没有严格模式与混杂模式的区别,HTML5 有相对宽松的 法,实现时,已经尽可能大的实现了向后兼容(HTML5 没有严格和混杂之分)。

总之,严格模式让各个浏览器统一执行一套规范兼容模式保证了旧网站的正常运行。

PWA使用过吗?serviceWorker的使用原理是啥?

渐进式网络应用(PWA)是谷歌在2015年底提出的概念。基本上算是web应用程序,但在外观和感觉上与原生app类似。支持PWA的网站可以提供脱机工作、推送通知和设备硬件访问等功能。

Service Worker是浏览器在后台独立于网页运行的脚本,它打开了通向不需要网页或用户交互的功能的大门。 现在,它们已包括如推送通知和后台同步等功能。 将来,Service Worker将会支持如定期同步或地理围栏等其他功能。 本教程讨论的核心功能是拦截和处理网络请求,包括通过程序来管理缓存中的响应。

代码输出结果

Promise.reject('err!!!')
  .then((res) => {
    console.log('success', res)
  }, (err) => {
    console.log('error', err)
  }).catch(err => {
    console.log('catch', err)
  })

输出结果如下:

error err!!!

我们知道,.then函数中的两个参数:

  • 第一个参数是用来处理Promise成功的函数
  • 第二个则是处理失败的函数

也就是说Promise.resolve('1')的值会进入成功的函数,Promise.reject('2')的值会进入失败的函数。

在这道题中,错误直接被then的第二个参数捕获了,所以就不会被catch捕获了,输出结果为:error err!!!'

但是,如果是像下面这样:

Promise.resolve()
  .then(function success (res) {
    throw new Error('error!!!')
  }, function fail1 (err) {
    console.log('fail1', err)
  }).catch(function fail2 (err) {
    console.log('fail2', err)
  })

then的第一参数中抛出了错误,那么他就不会被第二个参数不活了,而是被后面的catch捕获到。

数组去重

使用 indexOf/includes 实现

function unique(arr) {
    var res = [];
    for(var i = 0; i < arr.length; i++) {
        if(res.indexOf(arr[i]) === -1) res.push(arr[i]);
        // if(!res.includes(arr[i])) res.push(arr[i]);
    }
    return res;
}

使用 filter(forEach) + indexOf/includes 实现

// filter
function unique(arr) {
    var res = arr.filter((value, index) => {
        // 只存第一个出现的元素
        return arr.indexOf(value) === index;
    });
    return res;
}
// forEach
function unique(arr) {
    var res = [];
    arr.forEach((value) => {
        if(!res.includes(value)) res.push(value);
    });
    return res;
}

非 API 版本(原生)实现

function unique(arr) {
    var res = [];
    for(var i = 0; i < arr.length; i++) {
        var flag = false;
        for(var j = 0; j < res.length; j++) {
            if(arr[i] === res[j]) {
                flag = true;
                break;
            }
        }
        if(flag === false) res.push(arr[i]);
    }
    return res;
}

ES6 使用 Set + 扩展运算符(...)/Array.from() 实现

function unique(arr) {
    // return [...new Set(arr)];
    return Array.from(new Set(arr));
}

如何使用for...of遍历对象

for…of是作为ES6新增的遍历方式,允许遍历一个含有iterator接口的数据结构(数组、对象等)并且返回各项的值,普通的对象用for..of遍历是会报错的。

如果需要遍历的对象是类数组对象,用Array.from转成数组即可。

var obj = {
    0:'one',
    1:'two',
    length: 2
};
obj = Array.from(obj);
for(var k of obj){
    console.log(k)
}

如果不是类数组对象,就给对象添加一个[Symbol.iterator]属性,并指向一个迭代器即可。

//方法一:
var obj = {
    a:1,
    b:2,
    c:3
};

obj[Symbol.iterator] = function(){
    var keys = Object.keys(this);
    var count = 0;
    return {
        next(){
            if(count<keys.length){
                return {value: obj[keys[count++]],done:false};
            }else{
                return {value:undefined,done:true};
            }
        }
    }
};

for(var k of obj){
    console.log(k);
}


// 方法二
var obj = {
    a:1,
    b:2,
    c:3
};
obj[Symbol.iterator] = function*(){
    var keys = Object.keys(obj);
    for(var k of keys){
        yield [k,obj[k]]
    }
};

for(var [k,v] of obj){
    console.log(k,v);
}

替换元素的概念及计算规则

通过修改某个属性值呈现的内容就可以被替换的元素就称为“替换元素”。

替换元素除了内容可替换这一特性以外,还有以下特性:

  • 内容的外观不受页面上的CSS的影响:用专业的话讲就是在样式表现在CSS作用域之外。如何更改替换元素本身的外观需要类似appearance属性,或者浏览器自身暴露的一些样式接口。
  • 有自己的尺寸:在Web中,很多替换元素在没有明确尺寸设定的情况下,其默认的尺寸(不包括边框)是300像素×150像素,如
  • 在很多CSS属性上有自己的一套表现规则:比较具有代表性的就是vertical-align属性,对于替换元素和非替换元素,vertical-align属性值的解释是不一样的。比方说vertical-align的默认值的baseline,很简单的属性值,基线之意,被定义为字符x的下边缘,而替换元素的基线却被硬生生定义成了元素的下边缘。
  • 所有的替换元素都是内联水平元素:也就是替换元素和替换元素、替换元素和文字都是可以在一行显示的。但是,替换元素默认的display值却是不一样的,有的是inline,有的是inline-block。

替换元素的尺寸从内而外分为三类:

  • 固有尺寸: 指的是替换内容原本的尺寸。例如,图片、视频作为一个独立文件存在的时候,都是有着自己的宽度和高度的。
  • HTML尺寸: 只能通过HTML原生属性改变,这些HTML原生属性包括的width和height属性、的size属性。
  • CSS尺寸: 特指可以通过CSS的width和height或者max-width/min-width和max-height/min-height设置的尺寸,对应盒尺寸中的content box。

这三层结构的计算规则具体如下:(1)如果没有CSS尺寸和HTML尺寸,则使用固有尺寸作为最终的宽高。(2)如果没有CSS尺寸,则使用HTML尺寸作为最终的宽高。(3)如果有CSS尺寸,则最终尺寸由CSS属性决定。(4)如果“固有尺寸”含有固有的宽高比例,同时仅设置了宽度或仅设置了高度,则元素依然按照固有的宽高比例显示。(5)如果上面的条件都不符合,则最终宽度表现为300像素,高度为150像素。(6)内联替换元素和块级替换元素使用上面同一套尺寸计算规则。

对keep-alive的理解

HTTP1.0 中默认是在每次请求/应答,客户端和服务器都要新建一个连接,完成之后立即断开连接,这就是短连接。当使用Keep-Alive模式时,Keep-Alive功能使客户端到服务器端的连接持续有效,当出现对服务器的后继请求时,Keep-Alive功能避免了建立或者重新建立连接,这就是长连接。其使用方法如下:

  • HTTP1.0版本是默认没有Keep-alive的(也就是默认会发送keep-alive),所以要想连接得到保持,必须手动配置发送Connection: keep-alive字段。若想断开keep-alive连接,需发送Connection:close字段;
  • HTTP1.1规定了默认保持长连接,数据传输完成了保持TCP连接不断开,等待在同域名下继续用这个通道传输数据。如果需要关闭,需要客户端发送Connection:close首部字段。

Keep-Alive的建立过程

  • 客户端向服务器在发送请求报文同时在首部添加发送Connection字段
  • 服务器收到请求并处理 Connection字段
  • 服务器回送Connection:Keep-Alive字段给客户端
  • 客户端接收到Connection字段
  • Keep-Alive连接建立成功

服务端自动断开过程(也就是没有keep-alive)

  • 客户端向服务器只是发送内容报文(不包含Connection字段)
  • 服务器收到请求并处理
  • 服务器返回客户端请求的资源并关闭连接
  • 客户端接收资源,发现没有Connection字段,断开连接

客户端请求断开连接过程

  • 客户端向服务器发送Connection:close字段
  • 服务器收到请求并处理connection字段
  • 服务器回送响应资源并断开连接
  • 客户端接收资源并断开连接

开启Keep-Alive的优点:

  • 较少的CPU和内存的使⽤(由于同时打开的连接的减少了);
  • 允许请求和应答的HTTP管线化;
  • 降低拥塞控制 (TCP连接减少了);
  • 减少了后续请求的延迟(⽆需再进⾏握⼿);
  • 报告错误⽆需关闭TCP连;

开启Keep-Alive的缺点

  • 长时间的Tcp连接容易导致系统资源无效占用,浪费系统资源。

CDN的作用

CDN一般会用来托管Web资源(包括文本、图片和脚本等),可供下载的资源(媒体文件、软件、文档等),应用程序(门户网站等)。使用CDN来加速这些资源的访问。

(1)在性能方面,引入CDN的作用在于:

  • 用户收到的内容来自最近的数据中心,延迟更低,内容加载更快
  • 部分资源请求分配给了CDN,减少了服务器的负载

(2)在安全方面,CDN有助于防御DDoS、MITM等网络攻击:

  • 针对DDoS:通过监控分析异常流量,限制其请求频率
  • 针对MITM:从源服务器到 CDN 节点到 ISP(Internet Service Provider),全链路 HTTPS 通信

除此之外,CDN作为一种基础的云服务,同样具有资源托管、按需扩展(能够应对流量高峰)等方面的优势。

说一下HTTP和HTTPS协议的区别?

1、HTTPS协议需要CA证书,费用较高;而HTTP协议不需要
2、HTTP协议是超文本传输协议,信息是明文传输的,HTTPS则是具有安全性的SSL加密传输协议;
3、使用不同的连接方式,端口也不同,HTTP协议端口是80,HTTPS协议端口是443;
4、HTTP协议连接很简单,是无状态的;HTTPS协议是具有SSL和HTTP协议构建的可进行加密传输、身份认证的网络协议,比HTTP更加安全

说一下vue3.0你了解多少?

 <!-- 响应式原理的改变 Vue3.x 使用Proxy取代 Vue2.x 版本的Object.defineProperty -->
 <!-- 组件选项声明方式Vue3.x 使用Composition API setup 是Vue3.x新增的一个选项,他
    是组件内使用Composition API 的入口 -->
 <!-- 模板语法变化slot具名插槽语法 自定义指令 v-model 升级 -->
 <!-- 其它方面的更改Suspense支持Fragment(多个根节点) 和Protal (在dom其他部分渲染组建内容)组件
     针对一些特殊的场景做了处理。基于treeshaking优化,提供了更多的内置功能。 -->

代码输出问题

window.number = 2;
var obj = {
 number: 3,
 db1: (function(){
   console.log(this);
   this.number *= 4;
   return function(){
     console.log(this);
     this.number *= 5;
   }
 })()
}
var db1 = obj.db1;
db1();
obj.db1();
console.log(obj.number);     // 15
console.log(window.number);  // 40

这道题目看清起来有点乱,但是实际上是考察this指向的:

  1. 执行db1()时,this指向全局作用域,所以window.number 4 = 8,然后执行匿名函数, 所以window.number 5 = 40;
  2. 执行obj.db1();时,this指向obj对象,执行匿名函数,所以obj.numer * 5 = 15。