前端面试积累

414 阅读30分钟

面试缺漏

http相关、url访问发生了什么、内核以及兼容性问题、this指向问题、数组的遍历,响应式深入,keepalive,hoc高阶组件,hof高阶函数,vue自定义table组件表格

面试题视频

前端 Vue2/Vue3最新面试题,Vue.js原理

Vue3新特性

Vue3讲解文档

web前端面试-面试官系列

web前端面试-面试官系列

v-model实现原理

数据响应,都是通过数据的改变去驱动 DOM 视图的变化,而双向绑定除了数据驱动 DOM 外, DOM 的变化反过来影响数据,是一个双向关系,在 Vue 中,我们可以通过 v-model 来实现双向绑定。

v-model 即可以作用在普通表单元素上,又可以作用在组件上,它其实是一个语法糖,效果等同于v-bind+on-input

Vue2采用数据劫持结合发布者订阅者模式,通过Object.defineproperty()劫持数据setter和getter发生的改变,如果数据改变了(在setter中赋值),调用自身update方法,更新dom节点内的内容({{str}}),实现双向绑定

3.x的与2.x的核心思想一致,只不过数据的劫持使用Proxy而不是Object.defineProperty,只不过Proxy相比Object.defineProperty在处理数组和新增属性的响应式处理上更加方便

let nObj=new Proxy(obj,{
  //拦截get,当我们访问nObj.key时会被这个方法拦截到
  get: function (target, propKey, receiver) {
    console.log(`getting ${propKey}!`);
    return Reflect.get(target, propKey, receiver);
  },
  //拦截set,当我们为nObj.key赋值时会被这个方法拦截到
  set: function (target, propKey, value, receiver) {
    console.log(`setting ${propKey}!`);
    return Reflect.set(target, propKey, value, receiver);
  }
})

webpack 知识点

webpack学习梳理

webpack构建流程

webpack启动后,从entry开始,递归解析依赖的所有module,根据module.rules里配置的loader进行相应的转换。对module转换后,解析模块,生成一个一个的chunk,最后将所有的chunk转换成输出一个output。在构建过程中,webpack会执行plugin中的插件,完成plugin的任务

loader和plugin的区别

开发和生产的矛盾

开发

  1. 需要模块化
  2. 会使用新语法和框架(ts,es6,react)

生产

  1. 浏览器自身无法解析模块化
  2. 浏览器只认识js,有的老浏览器不认识es6
5大核心概念

入口(entry)、输出(output)、加载器(loader)、插件(plugins)、模式(mode)

module.exports = {
  // 入口
  entry: "",
  // 输出
  output: {},
  // 加载器
  module: {
    rules: [],
  },
  // 插件
  plugins: [],
  // 模式
  mode: "",
};

image-20230525103959336

配置文件

// Node.js的核心模块,专门用来处理文件路径
const path = require("path");
​
module.exports = {
  // 入口
  // 相对路径和绝对路径都行
  entry: "./src/main.js",
  // 输出
  output: {
    // path: 文件输出目录,必须是绝对路径
    // path.resolve()方法返回一个绝对路径
    // __dirname 当前文件的文件夹绝对路径
    path: path.resolve(__dirname, "dist"),
    // filename: 输出文件名
    filename: "main.js",
  },
  // 加载器
  module: {
    rules: [],
  },
  // 插件
  plugins: [],
  // 模式
  mode: "development", // 开发模式
};
配置图片资源

对图片资源性能优化

将小于某个大小的图片转化成 data URI 形式(Base64 格式)

const path = require("path");
​
module.exports = {
  entry: "./src/main.js",
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "main.js",
  },
  module: {
    rules: [
      {
        // 用来匹配 .css 结尾的文件
        test: /.css$/,
        // use 数组里面 Loader 执行顺序是从右到左
        use: ["style-loader", "css-loader"],
      },
      {
        test: /.less$/,
        use: ["style-loader", "css-loader", "less-loader"],
      },
      {
        test: /.s[ac]ss$/,
        use: ["style-loader", "css-loader", "sass-loader"],
      },
      {
        test: /.styl$/,
        use: ["style-loader", "css-loader", "stylus-loader"],
      },
      {
        test: /.(png|jpe?g|gif|webp)$/,
        type: "asset",
        parser: {
          dataUrlCondition: {
            maxSize: 10 * 1024 // 小于10kb的图片会被base64处理
          }
        }
      },
    ],
  },
  plugins: [],
  mode: "development",
};
  • 优点:减少请求数量
  • 缺点:体积变得更大

此时输出的图片文件就只有两张,有一张图片以 data URI 形式内置到 js 中了 (注意:需要将上次打包生成的文件清空,再重新打包才有效果)

Webpack 高级配置

提升开发体验

  • 使用 Source Map 让开发或上线时代码报错能有更加准确的错误提示。

提升 webpack 提升打包构建速度

  • 使用 HotModuleReplacement 让开发时只重新编译打包更新变化了的代码,不变的代码使用缓存,从而使更新速度更快。
  • 使用 OneOf 让资源文件一旦被某个 loader 处理了,就不会继续遍历了,打包速度更快。
  • 使用 Include/Exclude 排除或只检测某些文件,处理的文件更少,速度更快。
  • 使用 Cache 对 eslint 和 babel 处理的结果进行缓存,让第二次打包速度更快。
  • 使用 Thead 多进程处理 eslint 和 babel 任务,速度更快。(需要注意的是,进程启动通信都有开销的,要在比较多代码处理时使用才有效果)

减少代码体积

  • 使用 Tree Shaking 剔除了没有使用的多余代码,让代码体积更小。
  • 使用 @babel/plugin-transform-runtime 插件对 babel 进行处理,让辅助代码从中引入,而不是每个文件都生成辅助代码,从而体积更小。
  • 使用 Image Minimizer 对项目中图片进行压缩,体积更小,请求速度更快。(需要注意的是,如果项目中图片都是在线链接,那么就不需要了。本地项目静态图片才需要进行压缩。)

优化代码运行性能

  • 使用 Code Split 对代码进行分割成多个 js 文件,从而使单个文件体积更小,并行加载 js 速度更快。并通过 import 动态导入语法进行按需加载,从而达到需要使用时才加载该资源,不用时不加载资源。
  • 使用 Preload / Prefetch 对代码进行提前加载,等未来需要使用时就能直接使用,从而用户体验更好。
  • 使用 Network Cache 能对输出资源文件进行更好的命名,将来好做缓存,从而用户体验更好。
  • 使用 Core-js 对 js 进行兼容性处理,让我们代码能运行在低版本浏览器。
  • 使用 PWA 能让代码离线也能访问,从而提升用户体验。

ref是什么

方便快速获取dom

common.js

CommonJS模块的特点如下:

  • 所有代码都运行在模块作用域,不会污染全局作用域。
  • 模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果。要想让模块再次运行,必须清除缓存。
  • 模块加载的顺序,按照其在代码中出现的顺序。

回流和重绘

重绘(repaint) :屏幕的一部分要重绘。渲染树节点发生改变,但不影响该节点在页面当中的空间位置及大小。譬如某个div标签节点的背景颜色、字体颜色等等发生改变,但是该div标签节点的宽、高、内外边距并不发生变化,此时触发浏览器重绘(repaint)。

回流/重排(reflow) :也有称重排,当渲染树节点发生改变,影响了节点的几何属性(如宽、高、内边距、外边距、或是float、position、display:none;等等),导致节点位置发生变化,此时触发浏览器重排(reflow),需要重新生成渲染树。譬如JS为某个p标签节点添加新的样式:"display:none;"。导致该p标签被隐藏起来,该p标签之后的所有节点位置都会发生改变。此时浏览器需要重新生成渲染树,重新布局,即回流(reflow)。

注意:回流必将引起重绘,而重绘不一定会引起回流。

总结

  • 重绘不会引起dom结构和页面布局的变化,只是样式的变化,有重绘不一定有回流。
  • 回流/重排则是会引起dom结构和页面布局的变化,有回流就一定有重绘。 不管怎么说都是会影响性能

触发和优化

如何减少重排和重绘?

  • 最小化重绘和重排,比如样式集中改变,使用添加新样式类名 .classcssText
  • 批量操作 DOM,比如读取某元素 offsetWidth 属性存到一个临时变量,再去使用,而不是频繁使用这个计算属性;又比如利用 document.createDocumentFragment() 来添加要被添加的节点,处理完之后再插入到实际 DOM 中。
  • 使用 **absolute****fixed** 使元素脱离文档流,这在制作复杂的动画时对性能的影响比较明显。
  • 开启 GPU 加速,利用 css 属性 transformwill-change 等,比如改变元素位置,我们使用 translate 会比使用绝对定位改变其 lefttop 等来的高效,因为它不会触发重排或重绘,transform 使浏览器为元素创建⼀个 GPU 图层,这使得动画元素在一个独立的层中进行渲染。当元素的内容没有发生改变,就没有必要进行重绘。

你真的了解回流和重绘吗

闭包

闭包Closure是1964提出的一个计算机编程概念,论文《表达式的机器执行》的定义它包含控制和环境两个部分,在JavaScript中,以函数能够访问其定义时的环境中的变量的方式得以实现,内部的函数存在外部作用域的引用就会导致闭包

闭包指的是:能够访问另一个函数作用域的变量的函数

js作用域环境中访问变量的权利是由内向外的,内部作用域可以获得当前作用域下的变量并且可以获得当前包含当前作用域的外层作用域下的变量,反之则不能,也就是说在外层作用域下无法获取内层作用域下的变量,同样在不同的函数作用域中也是不能相互访问彼此变量的,那么我们想在一个函数内部也有限权访问另一个函数内部的变量该怎么办呢?闭包就是用来解决这一需求的,闭包的本质就是在一个函数内部创建另一个函数。

闭包的三个特性

①函数嵌套函数

②函数内部可以引用函数外部的参数和变量

③参数和变量不会被垃圾回收机制回收

function fn() {
    var a = 5;
    return function() {
        console.log(a);
    }
}

作用 一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。

闭包的2个注意点

  • 闭包不一定有return

    外部如果想使用闭包的变量,则此时需要return

    function outer() {
        const a = 1;
        function f() {
            console.log(a)
        }
        f()
    }
    outer()
    ​
    function outer() {
        const a = 10;
        return function f() {
            console.log(a)
        }
    }
    const fn = outer()
    fn()
    
    // 普通形式 统计函数调用的次数
    let i = 0      // i为全局变量,容易被修改
    function fn() {
        i++
        console.log(`函数调用了${i}次`)
    }
    ​
    // 闭包形式 统计函数调用的次数
    function count() {
        let i = 0
        return function fn() {
            i++
            console.log(`函数调用了${i}次`)
        } 
    }
    const fun = count() // i被私有(外界变量能使用但不能直接修改)
    
  • 不是所有内存泄漏都需要手动销毁 React很多闭包不能销毁

闭包的好处

①保护函数内的变量安全 ,实现封装,防止变量流入其他环境发生命名冲突,实现数据私有

②在内存中维持一个变量,可以做缓存(但使用多了同时也是一项缺点,消耗内存)

③匿名自执行函数可以减少内存消耗

坏处

①其中一点上面已经有体现了,就是被引用的私有变量不能被销毁,增大了内存消耗,造成内存泄漏,解决方法是可以在使用完变量后手动为它赋值为null;

②其次由于闭包涉及跨域访问,所以会导致性能损失,我们可以通过把跨作用域变量存储在局部变量中,然后直接访问局部变量,来减轻对执行速度的影响

js闭包学习

TS

1、TypeScript基础类型
  • boolean, number, string, undefined, null
  • 数组
let list1: number[] = [1, 2, 3]
// 数组泛型方式
let list2: Array<number> = [1, 2, 3]
  • 元组Tuple 元组类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同

    let t1: [string, number]
    t1 = ['hello', 10] // OK
    t1 = [10, 'hello'] // Error
    
  • 枚举 enum类型是对 JavaScript 标准数据类型的一个补充。 使用枚举类型可以为一组数值赋予友好的名字

    enum Color {
      Red,
      Green,
      Blue
    }
    
    // 枚举数值默认从0开始依次递增
    // 根据特定的名称得到对应的枚举数值
    let myColor: Color = Color.Green  // 0
    console.log(myColor, Color.Red, Color.Blue)
    
    enum Color {Red = 1, Green, Blue}
    let colorName: string = Color[2]
    
    console.log(colorName)  // 'Green'
    
  • any 有时候,我们会想要为那些在编程阶段还不清楚类型的变量指定一个类型。 这些值可能来自于动态的内容,比如来自用户输入或第三方代码库

  • void 某种程度上来说,void 类型像是与 any 类型相反,它表示没有任何类型。 当一个函数没有返回值时,你通常会见到其返回值类型是 void

    // void类型变量只能赋予 undefined 和 null
    let unusable: void = undefined
    
  • object

  • 联合类型(Union Types)表示取值可以为多种类型中的一种

    //需求1: 定义一个一个函数得到一个数字或字符串值的字符串形式值
    function toString2(x: number | string) : string {
      return x.toString()
    }
    //需求2: 定义一个一个函数得到一个数字或字符串值的长度
    function getLength(x: number | string) {
    
      // return x.length // error
    
      if (x.length) { // error
        return x.length
      } else {
        return x.toString().length
      }
    }
    
  • 类型断言

    /* 
    类型断言(Type Assertion): 可以用来手动指定一个值的类型
    语法:
        方式一: <类型>值
        方式二: 值 as 类型  tsx中只能用这种方式
    */
    
    /* 需求: 定义一个函数得到一个字符串或者数值数据的长度 */
    function getLength(x: number | string) {
      if ((<string>x).length) {
        return (x as string).length
      } else {
        return x.toString().length
      }
    }
    console.log(getLength('abcd'), getLength(1234))
    
  • 类型推断: TS会在没有明确的指定类型的时候推测出一个类型 有下面2种情况: 1. 定义变量时赋值了, 推断为对应的类型. 2. 定义变量时没有赋值, 推断为any类型

    /* 定义变量时赋值了, 推断为对应的类型 */
    let b9 = 123 // number
    // b9 = 'abc' // error
    
    /* 定义变量时没有赋值, 推断为any类型 */
    let b10  // any类型
    b10 = 123
    b10 = 'abc'
    
2、接口interface

接口是对象的状态(属性)和行为(方法)的抽象(描述)

interface IPerson {
  readonly id: number // 只读属性(赋值后不能改变)
  name: string
  age: number
  sex?: string  // 可选属性
}
3、type和interface

type 与 interface 的区别

JS

5分钟掌握ES6新特性技巧

1、简述同步和异步的区别,异步加载JS的方式有哪些
  • js是单线程同步的,只能同步(排队)执行
  • 不存在异步会发生代码阻塞
  • js通过事件循环(event loop)实现异步

总结:同步可以保证顺序一致,但是容易导致阻塞;异步可以解决阻塞问题,但是会改变顺序性,根据不同的需要去写你的代码。

js异步加载的五种方式

2、js精度丢失是什么造成的

计算机存储双精度浮点数需要先把十进制数转换为二进制的科学记数法的形式,然后计算机以自己的规则{符号位+(指数位+指数偏移量的二进制)+小数部分}存储二进制的科学记数法

因为存储时有位数限制(64位),并且某些十进制的浮点数在转换为二进制数时会出现无限循环,会造成二进制的舍入操作(0舍1入),当再转换为十进制时就造成了计算误差

/**
 * 精确加法
 */
function add(num1, num2) {
  const num1Digits = (num1.toString().split('.')[1] || '').length;
  const num2Digits = (num2.toString().split('.')[1] || '').length;
  const baseNum = Math.pow(10, Math.max(num1Digits, num2Digits));
  return (num1 * baseNum + num2 * baseNum) / baseNum;
}
3、null和undefined的差异

1.值是undefined的变量和未声明的变量,typeof运算结果都是“undefined”。运算符typeof运算null的类型时返回“object”。 2.通过禁止使用特殊值undefined,可以有效地确保只在一种情况下typeof才会返回“undefined”:当变量未声明时。这样就可以区别null和undefined。

null === undefined // false
null == undefined // true
4、暂时死区、变量提升、let, var, const 区别

暂时性死区:在使用let或const声明变量的时候,let或const声明前的这个变量不可用,为了减少运行时错误,防止在变量声明前就使用这个变量,从而导致意料之外的行为

变量提升:ES6之前我们一般使用var来声明变量,提升简单来说就是把我们所写的类似于var a = 123;这样的代码,声明提升到它所在作用域的顶端去执行

函数提升

//函数声明式
function bar () {}
//函数提升是整个代码块提升到它所在的作用域的最开始执行
//函数字面量式 
var foo = function () {}
//与变量提升方式相同
foo(); //1
 
var foo;
 
function foo () {
    console.log(1);
}
 
foo = function () {
    console.log(2);
}

foo(); //2
  • let声明的变量只在它所在代码块有效
  • 不存在变量提升
  • 暂时性死区
  • 不允许重复声明
  • const声明一个只读的常量,一旦声明,常量的值不允许改变
  • const的作用域和let相同,只在声明所在的块级作用域内有效
  • 不可重复声明
5、call, apply, bind

call() 方法在使用一个指定的 this 值和若干个指定的参数值的前提下调用某个函数或方法。

通过 call 方法我们做到了以下两点:

  • call 改变了 this 的指向,指向到 obj
  • fn 函数执行了。
Function.prototype.myCall = function (context) {
  // 判断调用对象
  if (typeof this !== "function") {
    throw new Error("Type error");
  }
  // 首先获取参数
  let args = [...arguments].slice(1);
  let result = null;
  // 判断 context 是否传入,如果没有传就设置为 window
  context = context || window;
  // 将被调用的方法设置为 context 的属性
  // this 即为我们要调用的方法
  context.fn = this;
  // 执行要被调用的方法
  result = context.fn(...args);
  // 删除手动增加的属性方法
  delete context.fn;
  // 将执行结果返回
  return result;
};

apply 就变得很简单了,他们没有任何区别,除了传参方式。

Function.prototype.myApply = function (context) {
  if (typeof this !== "function") {
    throw new Error("Type error");
  }
  let result = null;
  context = context || window;
  // 与上面代码相比,我们使用 Symbol 来保证属性唯一
  // 也就是保证不会重写用户自己原来定义在 context 中的同名属性
  const fnSymbol = Symbol();
  context[fnSymbol] = this;
  // 执行要被调用的方法
  if (arguments[1]) {
    result = context[fnSymbol](...arguments[1]);
  } else {
    result = context[fnSymbol]();
  }
  delete context[fnSymbol];
  return result;
};

bind 返回的是一个函数,这个地方可以详细阅读这篇文章,讲的非常清楚:解析 bind 原理,并手写 bind 实现

Function.prototype.myBind = function (context) {
  // 判断调用对象是否为函数
  if (typeof this !== "function") {
    throw new Error("Type error");
  }
  // 获取参数
  const args = [...arguments].slice(1),
  const fn = this;
  return function Fn() {
    return fn.apply(
      this instanceof Fn ? this : context,
      // 当前的这个 arguments 是指 Fn 的参数
      args.concat(...arguments)
    );
  };
};
6、js数据类型有哪些?哪些是基本数据类型哪些是引用数据类型?

基本数据类型(值类型):字符串(String)、数字(Number)、布尔(Boolean)、对空(Null)、未定义(Undefined)、BigInt、Symbol(es6新增)。

引用数据类型(对象类型):对象(Object)、数组(Array)、函数(Function)。

特殊的对象:正则(RegExp)和日期(Date)。

特殊类型:underfined 未定义、Null 空对象、Infinate 无穷、NaN 非数字

在 JS 中共有 8 种基础的数据类型,分别为: UndefinedNullBooleanNumberStringObjectSymbolBigInt

其中 SymbolBigInt 是 ES6 新增的数据类型,可能会被单独问:

  • Symbol 代表独一无二的值,最大的用法是用来定义对象的唯一属性名。
  • BigInt 可以表示任意大小的整数。

数据类型的判断

  • typeof:能判断所有值类型,函数。不可对 null、对象、数组进行精确判断,因为都返回 object
console.log(typeof undefined); // undefined
console.log(typeof 2); // number
console.log(typeof true); // boolean
console.log(typeof "str"); // string
console.log(typeof Symbol("foo")); // symbol
console.log(typeof 2172141653n); // bigint
console.log(typeof function () {}); // function
typeof (class something{}) // function
// 不能判别
console.log(typeof []); // object
console.log(typeof {}); // object
console.log(typeof null); // object
  • instanceof:能判断对象类型,不能判断基本数据类型,其内部运行机制是判断在其原型链中能否找到该类型的原型。比如考虑以下代码:
class People {}
class Student extends People {}

const vortesnail = new Student();

console.log(vortesnail instanceof People); // true
console.log(vortesnail instanceof Student); // true
  • Object.prototype.toString.call() :所有原始数据类型都是能判断的,还有 Error 对象,Date 对象等。
Object.prototype.toString.call(2); // "[object Number]"
Object.prototype.toString.call(""); // "[object String]"
Object.prototype.toString.call(true); // "[object Boolean]"
Object.prototype.toString.call(undefined); // "[object Undefined]"
Object.prototype.toString.call(null); // "[object Null]"
Object.prototype.toString.call(Math); // "[object Math]"
Object.prototype.toString.call({}); // "[object Object]"
Object.prototype.toString.call([]); // "[object Array]"
Object.prototype.toString.call(function () {}); // "[object Function]"

在面试中有一个经常被问的问题就是:如何判断变量是否为数组?

Array.isArray(arr); // true
arr.__proto__ === Array.prototype; // true
arr instanceof Array; // true
Object.prototype.toString.call(arr); // "[object Array]"
7、自执行函数

自执行函数,即定义和调用合为一体。我们创建了一个匿名的函数,并立即执行它,由于外部无法引用它内部的变量,因此在执行完后很快就会被释放,关键是这种机制不会污染全局对象。

// 下面2个括弧()都会立即执行  
(function () { /* code */ } ()) // 推荐使用这个  
(function () { /* code */ })() // 但是这个也是可以用的  
  
// 由于括弧()和JS的&&,异或,逗号等操作符是在函数表达式和函数声明上消除歧义的  
// 所以一旦解析器知道其中一个已经是表达式了,其它的也都默认为表达式了  
var i = function () { return 10; } ();  
true && function () { /* code */ } ();  
0, function () { /* code */ } ();  
  
// 如果你不在意返回值,或者不怕难以阅读
// 你甚至可以在function前面加一元操作符号  
!function () { /* code */ } ();  
~function () { /* code */ } ();  
-function () { /* code */ } ();  
+function () { /* code */ } ();  
  
// 还有一个情况,使用new关键字,也可以用,但我不确定它的效率  
// http://twitter.com/kuvos/status/18209252090847232  
new function () { /* code */ }  
new function () { /* code */ } () // 如果需要传递参数,只需要加上括弧()
8、http跨域方式有哪些,什么是同源策略,jsonp

九种跨域方式实现原理(完整版)

当协议、子域名、主域名、端口号中任意一个不相同时,都算作不同域。不同域之间相互请求资源,就算作“跨域”。

同源策略限制内容有:

  • Cookie、LocalStorage、IndexedDB 等存储性内容
  • DOM 节点
  • AJAX 请求发送后,结果被浏览器拦截了

但是有三个标签是允许跨域加载资源:

  • <img src=XXX>
  • <link href=XXX>
  • <script src=XXX>

特别说明两点:

第一:如果是协议和端口造成的跨域问题“前台”是无能为力的。

第二:在跨域问题上,仅仅是通过“URL的首部”来识别而不会根据域名对应的IP地址是否相同来判断。“URL的首部”可以理解为“协议, 域名和端口必须匹配”

这里你或许有个疑问:请求跨域了,那么请求到底发出去没有?

跨域并不是请求发不出去,请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了

服务端跨域处理:返回头属性加入'Access-control-allow-origin': '*'

9、cookie和seession

详解

Cookie通过在客户端记录信息确定用户身份,Session通过在服务器端记录信息确定用户身份

http协议:Http协议的规则是请求和响应的,请求-响应这种通信模式,即服务器不会主动搭理客户端,只是被动地响应客户端的请求。最重要的一个特性是无状态的,什么是无状态呢,就是每次请求对于服务器来说都是全新的请求,即使浏览器那边是同一个发出的,服务器是无法识别浏览器发出的请求是谁,是否之前请求过,所以也就导致了一个问题,既然是无状态的,那么我们登录购物网站,登录论坛,是如何确认到是我们的账号登录并操作的,这就是Cookie和Session作用所在。

会话机制(Session) :会话机制最主要的目的是帮助服务器记住客户端状态(标识用户,跟踪状态)。会话(Session)跟踪是Web程序中常用的技术,用来跟踪用户的整个会话。常用的会话跟踪技术是Cookie与Session。Cookie通过在客户端记录信息确定用户身份,Session通过在服务器端记录信息确定

一次完整的会话:一个会话可以帮助服务器断定一个客户端,那么反向推导得到的结论就是:当服务器无法断定客户端时,一次会话就结束了! 两种情况:服务器识别不到session(session失效) | 客户端没有携带cookie访问服务器(cookie失效) 。一次会话存在的基本原则:双方共存(cookie与session同时存在)

Cookie:Cookie实际上是一小段的文本信息。客户端第一次发出请求,请求服务器时,如果服务器需要记录该用户状态,就会使用response向客户端浏览器响应回一个Cookie。客户端会把Cookie保存起来,保存在浏览器的内存中

  • 会话Cookie (Session Cookie)
  • 持久性Cookie (Persistent Cookie)

会话Cookie(Session Cookie)被保存在浏览器的内存中,当浏览器关闭时,内存被释放,内存中的Cookie自然也就烟消云散。这样太麻烦了,关闭浏览器引发Cookie消失,下次还要重新登录。能不能向客户端响应持久性Cookie呢?只要设置Cookie的持久化时间即可!查看对应的API可以设置。 对于每种不同的浏览器,不管是会话Cookie还是持久性Cookie,都是相对独立的,每家浏览器厂商会话Cookie(保存在浏览器内存中)和持久性Cookie(保存在电脑某个浏览器设定的文件夹中)存储的位置是不同的。所以,当你用Chrome登录京东,你再用IE打开京东时,仍旧要登录,因为你没有带去标识用户信息的Cookie(不论是会话Cookie还是持久性Cookie,IE都拿不到Chrome的)。

  • 不设置MaxAge,默认响应会话Cookie(MaxAge<0),存在浏览器内存。Cookie随浏览器关闭而消失。
  • 设置MaxAge>0,响应持久性Cookie,会存在电脑硬盘的特定文件夹下(浏览器自定义的) ,这样你关闭浏览器后在有效时间内请求服务器仍然可以是登录状态。
  • 设置特定Cookie的MaxAge=0,则会删除已经存在客户端的此Cookie。

Session:有了Cookie,似乎已经能解决问题,为什么还需要Session?原因似乎可以列举很多,有可能是出于安全性和传输效率的考虑。首先,Cookie是存在客户端的,如果保存了敏感信息,会被其他用户看到。其次,如果信息太多,可能影响传输效率。 相比较Cookie存在客户端,Session则是服务端的东西。其本质上类似于一个大Map,里面的内容,以键值对的形式存储。 每个用户访问服务器都会建立一个session,那服务器是怎么标识用户的唯一身份呢?事实上,用户与服务器建立连接的同时,服务器会自动为其分配一个SessionId。其实,只要你在服务器端创建了Session,即使不写addCookie("JSESSIONID", id),JSESSIONID仍会被作为Cookie返回。 要注意的是,Session有个默认最大不活动时间:30分钟(可在配置文件中修改数值)。也就是说,创建Session并返回JSESSIONID给客户端后,如果30分钟内你没有再次访问,即使你下次再带着JSESSIONID来,服务端也找不到对应ID的Session了,因为它已经被销毁。此时你必须重新登录

  1. SessionId的重要性: 什么东西可以让你每次请求都把SessionId自动带到服务器呢?显然就是cookie了,如果你想为用户建立一次会话,可以在用户授权成功时给他一个唯一的cookie。当一个用户提交了表单时,浏览器会将用户的SessionId自动附加在HTTP头信息中,其实也就是请求中带着Cookie(这是浏览器的自动功能,用户不会察觉到),当服务器处理完这个表单后,将结果返回给SessionId所对应的用户。试想,如果没有 SessionId,当有两个用户同时进行注册时,服务器怎样才能知道到底是哪个用户提交了哪个表单呢。
  2. 储存需要的信息。服务器通过SessionId作为key,读写到对应的value,这就达到了保持会话信息的目的。

Session的序列化

所谓Session序列化,其实是一个默认行为。它存在的意义在于:比如现在有成千上万个用户在线,用户登录信息都在各自的Session中。当服务器不得不重启时,为了不让当前保存在服务器的Session丢失,服务器会将当前内存中的Session序列化到磁盘中,等重启完毕,又重新读取回内存。这些操作浏览器端用户根本感知不到,因为session还在,他不用重新登录。以Tomcat为例,服务器的Session都会被保存在work目录的对应项目下。关闭服务器时,当前内存中的session会被序列化在磁盘中,变成一个叫SESSIONS.ser的文件。

钝化和活化

自从改用Session后,由于Session都存在服务器端,当在线用户过多时,会导致Session猛增,无形中加大了服务器的内存负担。于是,服务器有个机制:如果一个Session长时间无人访问,为了减少内存占用,会被钝化到磁盘上。也就是说,Session序列化不仅仅是服务器关闭时才发生,当一个Session长时间不活动,也是有可能被序列化到磁盘中。当该Session再次被访问时,才会被反序列化。这就是Session的钝化和活化。可以在Tomcat的conf目录下的context.xml中配置(对所有项目生效。

还有个问题需要解决:Session被序列化了,存在Session中的值怎么办?比如存在session中的是一个entity类的对象?

HttpSession session= request.getSession();
session.setAttribute("user", new User("eekk", 26));

此时Session中有一个User对象,那么User对象去哪了?答案是,User从内存中消失,无法随Session一起序列化到磁盘。如果希望Session中的对象也一起序列化到磁盘,该对象必须实现序列化接口。

总结

  1. 我们需要知道cookie保存在客户端,session保存在服务端,cookie的产生是在服务端产生的,是浏览器第一次请求服务器,服务器响应给浏览器一个Cookie
  2. cookie只是一个通行证,但并不是安全的,任何安全的校验必须要在服务端上完成,cookie只是存在客户端上面的一个唯一标识它且由服务端定制的信息,本地可以改,但是不管怎么改,最后还是需要把它拿上发送给服务端进行匹配校验。
  3. session和cookie的存储都存在时效性,这是很有必要的
  4. 不管是cookie还是session,都是建立在安全性的大前提下,session中不仅仅有cookie的信息,同时会有该用户的相关重要且安全的信息存储,所以session是在服务器的,而cookie只是服务器将一些不重要的信息拿出来丢给客户的存在,以备以后快速匹配校验用。
10、javascript中定时器
  • setTimeout期望推迟(delay)ms后执行函数, setInterval 则是期望间隔(delay)ms就执行一次函数
  • 对于 周期性调度 的需求(即:重复的定时器),使用“嵌套的 setTimeout ”要比使用 setInterval 好。setInterval问题在于(1)某些间隔会被跳过;(2)多个定时器代码之间的间隔可能会比预期的小。
  • 使用 setInterval() 有些问题需要注意,比如当代码遇到阻塞时,循环定时的任务会被累积,阻塞结束时,这些累积的任务会无视间隔时间连续执行,因为它们已经被添加到队列中
  • setInterval() 还存在一个问题,当执行循环任务中的代码时,如果执行所需的时间大于循环任务的间隔时间,则当前循环任务会被取消。
//setTimeout()实现循环定时
let i = 0;
function timer = () => {
    i++;
    console.log(i);
    // 函数内定时器的回调函数继续调用timer()
    setTimeout(() => {
        timer()
    }, 1000)
}
// 启动函数
timer()

一般来说,最好不要使用 setInterval() ,而是使用 setTimerout() 来实现 setInterval() 的效果,这样可以有效的避免阻塞带来的问题。

11、JavaScript中==和===的区别

JS 中 == 是相同的意思,=== 代码严格相同值相同类型相同

===特殊情况

12、http的状态码,5系列代表什么?4系统代表什么?请求过长浏览器返回什么状态码?

常见状态码

  • 1xx 信息,代表服务器收到了请求 100客户端继续其请求 101切换协议
  • 2xx 成功,请求被服务器成功接收并处理 200请求成功(一般用于GET和POST) 201已创建,成功请求并创建了新的资源 202已接受请求,但未处理完城 204无内容。服务器成功处理,但未返回内容。在未更新网页的情况下,可确保浏览器继续显示当前文档 206部分内容。服务器成功处理了部分GET请求
  • 3xx 重定向 301永久移动。请求的资源已被永久的移动到新URI,返回信息会包括新的URI,浏览器会自动定向到新URI。今后任何新的请求都应使用新的URI代替 302临时移动。与301类似。但资源只是临时被移动。客户端应继续使用原有URI
  • 4xx 客户端错误请求失败 400客户端请求的语法错误,服务器无法理解 401请求要求用户的身份认证 403服务器理解请求客户端的请求,但是拒绝执行此请求 404服务器无法根据客户端的请求找到资源(网页)。通过此代码,网站设计人员可设置"您所请求的资源无法找到"的个性页面
  • 5xx 服务端错误 500服务器内部错误,无法完成请求 502作为网关或者代理工作的服务器尝试执行请求时,从远程服务器接收到了一个无效的响应 503由于超载或系统维护,服务器暂时的无法处理客户端的请求。延时的长度可包含在服务器的Retry-After头信息中 504充当网关或代理的服务器,未及时从远端服务器获取请求

状态码第一位数字决定了不同的响应状态,如下:

  • 1 表示消息
  • 2 表示成功
  • 3 表示重定向
  • 4 表示请求错误
  • 5 表示服务器错误

1xx

代表请求已被接受,需要继续处理,这类响应是临时响应,只包含状态行和某些可选的响应信息,并一空行结束

常见的有:

  • 100 (客户继续发送请求,这是临时响应) 这个临时响应是用来通知客户端它的部分请求已经被服务器接收,且仍未被拒绝。客户端印当据需发送请求的剩余部分,或者如果请求已经完成,忽略这个响应,服务器必须在请求完成后向客户端发送一个最终响应
  • 101 服务器根据客户端的请求切换协议,主要用于 websocketHTTP2 升级

2xx

代表请求已成功被服务器接收,处理,并接受

  • 200 (成功) 请求已成功,请求所希望的响应头或数据体将随此响应返回
  • 201 (已创建)请求成功并且服务器创建了新的资源
  • 202 (已创建)服务器已经接受请求,但尚未处理
  • 203 (非授权信息)服务器已成功处理请求,但返回的信息可能来自另一来源
  • 204 (无内容)服务器成功处理请求,但没有返回任何内容
  • 205 (重置内容)服务器成功处理请求,但没有返回任何内容
  • 206 (部分内容)服务器成功处理了部分请求

3xx

表示要完成请求,需要进一步操作,通常这些状态代码用来重定向

  • 300 (多种选择)针对请求,服务器可执行多种操作。
  • 301 (永久移动)请求的网页已永久移动到新位置。
  • 302 (临时移动)服务器目前从不同位置的网页响应请求,但请求者应该继续使用原有位置来进行以后的请求
  • 303 (查看其它位置)请求者应当对不同位置使用单独的 GET 请求来检索响应时,服务器返回此代码
  • 305 (使用代理)请求者只能使用代理访问请求的网页。
  • 307 (临时重定向)服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求

4xx

代表了客户端看起来可能发生了错误,妨碍了服务器的处理

  • 400 (错误请求)服务器不理解请求的语法
  • 401 (未授权)请求要求身份验证。
  • 403 (禁止)服务器拒绝请求
  • 404 (未找到)服务器找不到请求的网页
  • 405 (方法禁用)禁用请求中指定的方法
  • 406 (不接受)无法使用请求的内容特性响应请求的网页
  • 407 (需要代理授权)此状态代码与 401(未授权)类似,但指定请求者应当授权使用代理
  • 408 (请求超时)服务器等候请求时发生超时

5xx

表示服务器无法完成明显有效的请求。这类状态代码代表了服务器在处理请求的过程中有错误或异常状态发生

  • 500 (服务器内部错误)服务器遇到错误,无法完成请求
  • 501 (尚未实施)服务器服务器不具备完成请求的功能
  • 502 (错误网关Bad Gateway)服务器作为网关或代理,从上游服务器收到无效响应
  • 503 (服务不可用)服务器目前无法使用,(由于超载或停机维护)
  • 504 (网关超时Gateway Time-out)服务器作为网关或代理,但是没有及时从上游服务器收到请求
  • 505HTTP 版本不受支持)服务器不支持请求中所用的 HTTP 协议版本
13、HTTP和HTTPS的区别?HTTP的几种请求方法用途?get请求和post请求有什么区别

HTTP协议以明文方式发送内容,不提供任何方式的数据加密。HTTP协议不适合传输一些敏感信息,比如:信用卡号、密码等支付信息。https则是具有安全性的ssl加密传输协议。

http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。并且https协议需要到ca申请证书。HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,要比http协议安全。

HTTP的几种请求方法用途

get 和 post 的区别:

  1. get是从服务器上获取数据,post是向服务器传送数据。
  2. GET请求把参数包含在URL中,将请求信息放在URL后面,POST请求通过request body传递参数,将请求信息放置在报文体中。
  3. get传送的数据量较小,不能大于2KB。post传送的数据量较大,一般被默认为不受限制。但理论上,IIS4中最大量为80KB,IIS5中为100KB
  4. get安全性非常低,get设计成传输数据,一般都在地址栏里面可以看到,post安全性较高,post传递数据比较隐私,所以在地址栏看不到, 如果没有加密,他们安全级别都是一样的,随便一个监听器都可以把所有的数据监听到。
  5. GET请求能够被缓存,GET请求会保存在浏览器的浏览记录中,以GET请求的URL能够保存为浏览器书签,post请求不具有这些功能。
  6. HTTP的底层是TCP/IP,GET和POST的底层也是TCP/IP,也就是说,GET/POST都是TCP链接。GET和POST能做的事情是一样一样的。你要给GET加上request body,给POST带上url参数,技术上是完全行的通的。
  7. GET产生一个TCP数据包,对于GET方式的请求,浏览器会把http header和data一并发送出去,服务器响应200(返回数据);POST产生两个TCP数据包,对于POST,浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok(返回数据),并不是所有浏览器都会在POST中发送两次包,Firefox就只发送一次
14、JS中内存泄漏的几种情况

内存泄漏 由于疏忽或错误造成程序未能释放已经不再使用的内存

意外的全局变量('use strict'使用严格模式解决)

'use strict'
function fun() {
    //a = 10 //a为全局变量
    //console.log(a)
    this.b = 20 // this为window
}
fun()
console.log(b)

定时器(不用的时候及时清除定时器)/未清除的dom元素(domel = null)

var main = document.querySelector('.main');
var time = setInterval(() => {
    var test = document.querySelector('.test');
    main.removeChild(test);
    console.log(test); //test内存空间还存在
    test=null;
    if (!test) {
        cleartInterval(time)
    }
}, 2000)

闭包(不用时为null)

function fun(name){
    function fun1() {
        console.log(name);
    }
    return fun1;
}
var fn = fun('张三')
fn()
fn()
fn()
fn = null
fn() //报错

事件监听(不使用时取消事件监听)

15、手写深拷贝

深拷贝方式:

  1. JSON.stringify()将字符串转化成json格式数据,JSON.parse()将json字符串转化成对象,对象中如果有函数,undefined,symbol,使用过JSON.stringfy()处理之后,都会消失
  2. 使用lodash的cloneDeep(value)
  3. 手写深拷贝

惊艳面试官的深拷贝

手写深拷贝哈默

/**
 * 深拷贝
 * @param {Object} obj 要拷贝的对象
 */function deepClone(obj = {}) {
    if (typeof obj !== 'object' || obj == null) { //基本数据类型或函数
        return obj 
    }
    let result;
    if (obj instanceof Array) { //数组
        result = []
    } else { // 对象
        result = {}
    }
    
    for(let key in obj) {
        if(obj.hasOwnProperty(key)){
            result[key] = deepClone(obj[key]);   
        }
    }
    
    return result
}
​
/**
 * 深拷贝
 * @param {Object} obj 要拷贝的对象
 * @param {Map} map 用于存储循环引用对象的地址
 */function deepClone(obj = {}, map = new Map()) {
  if (typeof obj !== "object") {
    return obj;
  }
  if (map.get(obj)) {
    return map.get(obj);
  }
​
  let result = {};
  // 初始化返回结果
  if (
    obj instanceof Array ||
    // 加 || 的原因是为了防止 Array 的 prototype 被重写,Array.isArray 也是如此
    Object.prototype.toString(obj) === "[object Array]"
  ) {
    result = [];
  }
  // 防止循环引用
  map.set(obj, result);
  for (const key in obj) {
    // 保证 key 不是原型属性
    if (obj.hasOwnProperty(key)) {
      // 递归调用
      result[key] = deepClone(obj[key], map);
    }
  }
​
  // 返回结果
  return result;
}

使用 WeakMap 对已遍历的对象保存吗,当递归调用时,判断 WeakMap 中是否已经存在,若已经存在就直接返回,避免循环调用

var copyObj = JSON.parse(JSON.stringify(obj)); 但是后来发现JSON.stringify() 有以下几个特点:

  • 非数组对象的属性不能保证以特定的顺序出现在序列化后的字符串中。
  • 布尔值、数字、字符串的包装对象在序列化过程中会自动转换成对应的原始值。
  • undefined、任意的函数以及 symbol 值,在序列化过程中会被忽略(出现在非数组对象的属性值中时)
  • 或者被转换成 null(出现在数组中时)。
  • 所有以 symbol 为属性键的属性都会被完全忽略掉,即便 replacer 参数中强制指定包含了它们。
  • 不可枚举的属性会被忽略
16、split/splice/slice/join核心
  • slice 新数组 = 原数组.slice(开始位置的索引, 结束位置的索引) :

    截取功能

    • 截取数组为主,也可以截取字符串
    • 返回新的数组,包含截取的元素
    • 不改变原数组
  • splice 新数组 = 原数组.splice(起始索引index, 需要删除的个数)

    新数组 = 原数组.splice(起始索引index, 需要删除的个数, 新的元素1, 新的元素2...):

    数组增删查改

    • 只能对数组增删查改,字符串无效
    • 返回新的数组,内容是被删除的元素
    • 会改变原数组
  • split 新的数组 = str.split(分隔符):字符串 => 数组

    • 字符串的方法,不是数组的方法。
    • 返回一个字符串数组。
  • join 新的字符串 = 原数组.join(参数); // 参数选填--连接符:数组 => 字符串

17、forEach跳出循环

forEach的参数是一个匿名函数,是一个callback,每次forEach都是执行一次函数

try catch跳出循环

let k = null
try{
    arr.forEach(n => {
        if (n == 2) {
            k = n;
            throw Error();
        }
    })
} catch(e) {
    
}
18、promise.then()和setTimeout、宏任务微任务

宏任务:[task1,task2,task3…]颗粒度大 实时性不强,要排队 微任务:缩小颗粒度[task1[m1…],task2[m1…],task3[m1…]],直接插队,实时性强

微任务:在上一个宏任务之后,下一个宏任务之前执行。 第一个 script 标签的代码是第一个宏任务

promise本身里面是同步任务,await右边是同步任务,promise.then()/.catch()是微任务,setTimeout是宏任务。 异步任务大致分为:微任务(micro task,如:process.nextTick(node)、Async/Await、promise.then/catch、Object.observe、MutaionObserver等)和宏任务(macro task,如:script(代码块)、DOM事件、AJAX请求、setTimeout、setInterval、I/O等)。

console.log(111);
setTimeout(() => {
    console.log("setTimeout");
})
console.log(222);
let p = new Promise((resolve, reject) => {
    resolve('aa');
    console.log(333);
})
console.log(p);
p.then(res => {
    console.log(444, res);
}, err => {
    console.log(err);
})
console.log(555);
// 111 -> 222 -> 333 -> 555 -> 444 -> setTimeout

Promise和setTimeout执行顺序

  • macro-task(宏任务) :script,setTimeout,setInterval
  • micro-task(微任务) :Promise,process.nextTick

关于async/await、promise和setTimeout的执行顺序

19、Dom位置属性(横轴为例)

clientWidth:元素內部宽度 = width + padding

offsetWidth:元素內部宽度 = width + padding + border

clientLeft:元素左边框宽度

offsetLeft:元素左外边框与最近有定位的父元素的距离

(一下与事件相关)

clientX:鼠标相对于浏览器窗口可视区的x坐标(横向)

offsetX:鼠标相对于事件源元素的x坐标

pageX:鼠标相对于文档的x坐标,而非窗口坐标(计算滚动距离)

screenX:鼠标相对于显示器屏幕左侧的x坐标

getBoundingClientRect().left:元素左边框相对于可视区的距离,可能为负值

20、说出集中js获取数组最后一个元素的方法
  • arr.pop() 影响原数组
  • arr[arr.length - 1]
  • arr.slice(-1)
  • arr.at(-1)
21、原型和原型链

原型:函数(除箭头函数)都有prototype属性,称之为原型,因为这个属性值是个对象,也称为原型对象

作用:存放一些属性和方法 在JavaScript中实现继承

proto : 每个对象都有__proto__属性

作用:指向原型

arr.proto = Array.prototype

原型链:对象都有__proto__属性,这个属性指向它的原型对象,原型对象也是对象,也有__proto__属性,指向原型对象的原型对象,这样一层一层形成的链式结构称作原型链,最顶层找不到则返回null

22、跳出循环的方法

跳出循环的三种方法

map/forEach以及return和break的区别

23、js面向对象的理解

浅谈javascript面向对象理解

24、js垃圾回收机制
  • 内存泄漏 定义:程序中己动态分配的堆内存由于某种原因程序未释放或无法释放引发的各种问题。 js中可能出现的内存泄漏情况:结果:变慢,崩溃,延迟大等 js中可能出现的内存泄漏原因 全局变量 dom 清空时,还存在引用 定时器未清除 子元素存在引起的内存泄露

  • JS垃圾回收机制是怎样的?

    1.概述

    js的垃圾回收机制是为了防止内存泄漏(已经不需要的某一块内存还一直存在着),垃圾回收机制就是不停歇的寻找这些不再使用的变量,并且释放掉它所指向的内存。 在JS中,JS的执行环境会负责管理代码执行过程中使用的内存。

    2.变量的生命周期

    当一个变量的生命周期结束之后,它所指向的内存就会被释放。js有两种变量,局部变量和全局变量,局部变量是在他当前的函数中产生作用,当该函数结束之后,该变量内存会被释放,全局变量的话会一直存在,直到浏览器关闭为止。

    3.js垃圾回收方式 有两种方式: 标记清除、引用计数

    标记清除:大部分浏览器使用这种垃圾回收,当变量进入执行环境(声明变量)的时候,垃圾回收器将该变量进行了标记,当该变量离开环境的时候,将其再度标记,随之进行删除。

    引用计数:这种方式常常会引起内存的泄露,主要存在于低版本的浏览器。它的机制就是跟踪某一个值得引用次数,当声明一个变量并且将一个引用类型赋值给变量得时候引用次数加1,当这个变量指向其他一个时引用次数减1,当为0时出发回收机制进行回收。

25、js的数组方法

JS 数组方法整理

26、set 和 weakset,map 和 weakmap

Set是ES6新的数据结构,类似数组,但成员的值是唯一的,没有重复的值。

let s = new Set()
s.add(1)
s.add(2)
s.add(2)
s.add(3)
for(let item of s){
  console.log(item) //1,2,3
}

let ss=new Set([1,2,3,3])
[...ss] //1,2,3

//去重
[...new Set(array)],Array.from(new Set(array))

Weakset和Set结构类似,也是不重复的值的集合,但WeakSet的成员只能是对象,不可遍历,没有size属性(因为WeakSet的成员都是弱引用,随时可能消失,成员是不稳定的),垃圾回收机制只在乎强引用。

作用:使用ws储存DOM节点,就不用担心节点从文档移除时,会引发内存泄漏(即在被移除的节点上绑定的click等事件)

let array=[[1,2],[3,4]]
let ws=new WeakSet(array)  //{[1,2],[3,4]}

let array=[1,2,3,4]
let ws=new WeakSet(array) //报错

map本质上是键值对的集合,可以是任意的数据类型,方法有size,set,get,delete,has,clear WeakMap只接受对象作为键名(null除外),不接受其他类型的值作为键名。WeakMap的键名所指向的对象是弱引用,不计入垃圾回收机制。无法被遍历,因为没有size。无法被清空,因为没有clear(),跟WeakSet相似

let myElement = document.getElementById('logo');
let myWeakmap = new WeakMap();

myWeakmap.set(myElement, {timesClicked: 0});

myElement.addEventListener('click', function() {
  let logoData = myWeakmap.get(myElement);
  logoData.timesClicked++;
}, false);

上面代码中,myelement是一个 DOM 节点,每当发生click事件,就更新一下状态。我们将这个状态作为键值放在 WeakMap 里,对应的键名就是myelement。一旦这个 DOM 节点删除,该状态就会自动消失,不存在内存泄漏风险。

27、强引用和弱引用

弱引用:垃圾回收机制不考虑对该对象的引用

强引用:垃圾回收机制考虑对该对象的引用

文章

28、js作用域考题
  • 除了函数外,js没有块级作用域

  • 作用域链:內部可以访问外部的变量,但是外部不能访问內部的变量。如果內部有,优先查找內部变量

  • 注意声明时var有没有写

    (function foo() {
        var a = b = 10
    })()
    console.log(a) // 报错 
    console.log(b) // 10 window.b
    
  • js有变量提升机制(变量悬挂声明) 先提升再if判断

  • 优先级 声明变量 > 声明普通函数 > 参数 > 变量提升

29、对象合并的方法
  • object.assign()
  • obj = {...obj1, ...obj2}
  • 遍历对象赋值

VUE

1、vue生命周期

vue2.x系统自带8个

beforeCreate created beforeMount mounted beforeUpdate updated beforeDestory destoryed

一旦进入组件执行 beforeCreate(没有dom和data)

created beforeMount(没有dom,有data)

mounted(获得了dom,有data)

created中如何获取dom

只要写异步代码就可以获取dom 如setTimeout, 请求, promise.then()

vue系统内置的this.$nextTick

引入子组件父子执行顺序 父beforeCreate created beforeMount => 子beforeCreate created beforeMount mounted => 父mounted

父beforeUpdate => 子beforeUpdate => 子updated => 父updated

为什么不在beforeCreate中发送请求

axios请求异步,在beforeCreate created beforeMount mounted(都是同步任务)执行后才有返回,如果请求方法是在methods里封装好的,beforeCreate获取不到methods,如果不封装axios请求可以得到返回,created可以拿到methods

created还是mounted中发送请求

created中发送请求则父组件先得到返回数据后子组件得到

mounted中发送请求则子组件先得到返回数据后父组件得到

按照实际项目业务情况决定

加入keep-alive会执行那些生命周期

keep-alive缓存组件,会增加activated 和 deactivated生命周期

在什么情况下用过哪些生命周期,使用场景

created 父组件先显示/单一组件情况下请求

mounted 子组件先显示/同步需要dom情况下请求

activated 判断id是否相同,不相同发起请求

destroyed 关闭页面记录用户操作如视频播放记录

image-20221219141751117

2、挂载的时候涉及的生命周期函数是什么

编译template返回render渲染函数 => beforeMount :生命周期钩子函数被执行 => 把虚拟DOM和渲染的数据一并挂到真实DOM上 => 挂载完毕,mounted:生命周期钩子函数被执行

Vue生命周期详解

3、MVVM

web1.0时代:文件全在一起,前后端代码融合在一起

问题:1、前后端都是一个人开发(责任不能细分) 2、项目不好维护 3、html, css, js静态页面不出来无法开展工作

解决: 出现mvc框架

web2.0时代:ajax出现,前后端可以分离

解决问题:前后端各自工作,后端写接口,前端开发页面

问题:1、html, css, js 都在一个页面中,单个页面内容多不好维护

出现前端MVC,MVVM框架

解决问题:把一个特别大的页面,进行拆分(组件化),单个组件维护

view <=> ViewModel <=> model

view: 视图 model: 数据 ViewModel: 视图模型层(源码)

4、v-if和v-show的区别?

相同点

  • 当表达式为true的时候,都会占据页面的位置
  • 当表达式都为false时,都不会占据页面位置

区别

  • 控制手段不同
  • 编译过程不同
  • 编译条件不同

控制手段:v-show隐藏则是为该元素添加css--display:nonedom元素依旧还在。v-if显示隐藏是将dom元素整个添加或删除

编译过程:v-if切换有一个局部编译/卸载的过程,切换过程中合适地销毁和重建内部的事件监听和子组件;v-show只是简单的基于css切换

编译条件:v-if是真正的条件渲染,它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。只有渲染条件为假时,并不做操作,直到为真才渲染

  • v-showfalse变为true的时候不会触发组件的生命周期
  • v-iffalse变为true的时候,触发组件的beforeCreatecreatebeforeMountmounted钩子,由true变为false的时候触发组件的beforeDestorydestoryed方法

性能消耗:v-if有更高的切换消耗;v-show有更高的初始渲染消耗;

5、v-if和v-for同时使用在同一个标签上的表现?

在vue2中,v-for优先级比v-if高 在vue3中,v-if优先级比v-for高

一般不能一起使用,可以通过computed计算数组,filter过滤数组

可以把v-if移动至容器元素上(包裹一层div、li)

6、vue组件通信(传值)
  • 父传后代

    // 父组件绑定数据
    <comp :name="name"></comp>
    // 子组件props接收
    props:{
    	name: {
    		type:String,
    		default:''
    	}
    }
    //父传子很方便,传给孙组件很麻烦
    //子不能直接修改父组件数据
    
    //子组件使用父组件的数据
    this.$parent.xxx
    //子可以直接修改父组件数据
    
    //provide/inject依赖注入
    
    //父组件
    provide() {
    	return {
    		val: 'content'
    	}
    }
    
    //后代组件
    inject: ['val']
    
    //父组件直接传值给后代组件
    
  • 后代传父

    //子组件传父组件//子组件
    this.$emit('change', val)
    ​
    //父组件
    <father @change='handleChange'></father>
    ​
    methods: {
        handleChange(val) {
            console.log(val)
        }
    }
    
    //子组件传父
    this.$children.xxx
    ​
    子组件调用时绑定ref
    this.$ref.xxx
    //可直接修改子组件的值
    
  • 同级传值

    // bus传递  新建bus.js
    import Vue from 'vue'export default new Vue();
    
7、vue的路由模式及区别
  1. Hash 模式: 使用URL 的 hash 值来作为路由,支持所有浏览器

    1.1 url 路径会出现 # 号字符 1.2 hash值不包括在HTTP请啊求中,它是交由前端路由处理,所以改变 hash 值时不会刷新页面,也不会向服务器发送请求

    1.3 hash值的改变会触发 hashchange 事件

  2. History 模式:以HTML5 History API 和服务器配置,参考官网中HTML5 History 模式tract

    2.1 整个地址重新加载,可以保存历史记录,方便前进后退

    2.2 使用 HTML5 API(旧浏览器不支持)和 HTTP 服务端配置,没有后台配置的话,页面刷新时会出现 404

  3. Abstract 模式:支持所有 javascript 运行模式,如果发现没有浏览器的API,路由会自动强制进入这个模式。

history模式

  • 找不到路由时,会给后端发送一次请求
  • 前端打包自测看不到内容,需要加配置

hash模式

  • 路由上有#
  • 找不到路由时,不会发送请求
  • 前端打包自测可以看到内容

子路由、路由传值

<router-link to="listB/周杰伦/40岁">listB</router-link>
<router-link :to="{name:'listB',params:{name:'渣渣辉',age:50}}">动态路由name/params</router-link>

//或者js里面跳转
this.$router.push({
	name: "listB",
	params: {
		name: "渣渣辉",
		age: "40岁"
	}
}); 相当于post

 this.$router.push('listA')
 this.$router.push({
          path: "listA",
          query: {
            name: "渣渣辉",
            age: "40岁"
          }
        }); // 相当于Get,参数名会出现在地址栏上

动态路由

8、vue-router的钩子函数

vue-router的钩子函数

9、vue的filter的理解与用法

vue 过滤器 filter 定义和使用

10、如何封装一个vue组件
  1. 设计良好的接口:好的组件应该提供清晰的接口和文档,使其他开发人员能轻松了解如何使用和扩展
  2. 考虑复用性:组件应该尽可能通用,以便在不同的项目和情况下重复使用
  3. 可配置性:封装组件应该允许用户配置不同的选项和属性,以满足不同的需求
  4. 避免过度封装:不要将太多的逻辑放在一个组件中,以免让它变得过于复杂。在设计组件时应该考虑组件的单一职责原则
  5. 考虑性能:组件的性能应该是可接受的,不会对整个应用程序造成过大的负担。可以使用一些性能优化技术,如懒加载、虚拟滚动等提高性能
  6. 测试和调试:在封装组件时应该考虑到测试和调试,以确保组件的正确性和稳定性
  7. 文档和示例:为组件提供清晰的文档和示例帮助更好的了解使用组件
11、data为什么是一个函数

Object是引用数据类型,如果不用function返回,每个组件的data都是内存的同一个地址,一个数据改变了其他也改变了;

(引用数据类型--对堆内存地址的映射)

JavaScript只有函数构成作用域,data是一个函数时,每个组件实例都有自己的作用域,每个实例相互独立,不会相互影响。

12、vue响应式原理
  • vue会遍历此data中对象的所有属性
  • 使用Object.defineProperty把这些属性全部转换为getter和setter
  • 每个组件实例都有watcher对象,它会在组件渲染中把属性记录为依赖,依赖的setter被调用时通知watcher重新计算
  • image-20221101094552826
13、computed和watch,和methods的区别

computed计算属性,依赖的属性变化时,数据才更新/计算结果有缓存,依赖的属性没有变化时直接调用缓存值/必须有return/computed不能出现异步

watch 监听数据变化,执行对应操作/没有缓存,监听值变化都会调用函数/watch不用return/watch可以异步

computed vs methods

computed是有缓存的,methods没有缓存

computed vs watch

watch时监听,数据或路由发生了改变才可以响应

computed计算某一个属性的改变,如果某一个值改变了会监听到进行返回,watch是当前监听到的数据改变了,执行內部代码

14、mutations和actions的区别

mutations必须是同步函数

actions可以包含异步操作

mutations可以放入函数,actions也可以放入函数,但是我们一般在mutations中放入函数而在actions中提交mutations

15、.sync修饰符

.sync 修饰符可以实现子组件与父组件的双向绑定,并且可以实现子组件同步修改父组件的值

// vue2写法
<comp :count.sync ="count"></comp>
//等同于
<comp :count ="count" @update:count = "val => {return count = val}"></comp>

vue3中v-model整合了.sync

<comp v-model:count ="count"></comp>
16、slot插槽

主要用于组件封装

  • 匿名插槽:插槽没有名字

    <slot></slot>
    
  • 具名插槽:插槽有名字

    // 待插入组件
    <slot name="slotName"></slot>
    
    // 插入的组件
    <template #slotName>
    	<div>
            content
        </div>
    </template>
    
  • 作用域插槽:传值

    // 待插入组件
    <slot name="slotName" :arr="arr" :str="str"></slot>
    
    // 插入的组件
    <template #slotName="{arr, str}">
    	<div>
            content
        </div>
    </template>
    
17、props 和 data的优先级

根据源码中位置判断

props > methods > data > computed > watch

18、如何找到父组件和根组件

this.parentthis.parent this.root

19、如何封装组件

回答核心:组件一定要有难点,涉及知识点:slot、组件通信

一些Vue封装组件技巧:

  1. $attrs 简化多层组件之间props传值;
  2. $listeners 简化多层组件之间事件传递;
  3. $Slots 更多拓展自定义组件传值,包括自定义html元素,及对象;
  4. props validator 增强组件传值稳健性,可自定义业务代码效验参数;
  5. $refs 对外提供API 增强组件灵活度和可控性;
20、Vuex 和 Pinia相关

Vuex的属性和用法:

state ==> 全局共享属性 this.$store.state.xxx 可以修改 / 辅助函数mapState 不可修改

getters ==> 针对state的数据进行二次计算,getters不可修改,绑定v-model会报错 this.$store.getters.xxx/ 辅助函数mapGetters

mutations ==> 存放同步方法。所包含的函数没有返回值,只操作state

actions ==> 存放异步方法,提交mutations。所包含的函数没有返回值

modules ==> 把Vuex进行模块间划分

mutations和actions的区别:都是存放方法的,方法没有return。mutations是同步的,actions是异步的,返回promise对象,提交异步执行mutations方法

Vuex本身不是持久化存储的数据,Vuex是一个状态管理仓库,存放全局属性。可以通过写localStorage或使用vuex-persistedstate插件进行持久化存储

Pinia

state

getters

mutations(移除)

actions(支持同步和异步)

21、导航守卫
  1. 全局守卫 (判断是否登录、权限)

    beforeEach 路由进入前

    afterEach 路由进入后

  2. 路由独享守卫 (某些页面判断登录)

    beforeEnter 路由进入前

  3. 组件内守卫

    beforeRouteEnter 路由进入前

    beforeRouteUpdate 路由更新前

    beforeRouteLeave 路由离开前

22、$set

this.$set(obj, key, value)

数据(通常为arr、obj中某个属性更新)更新了视图{{arr}}/{{obj}}不更新时使用强制更新

23、$nextTick是什么

在下次dom更新循环结束后执行延迟回调。Vue的dom监听数据变化后缓存在更新循环机制中等待更新,如果需要立即获取更新后dom则使用nextTick

$nextTick(() => {})返回的参数[函数],是一个异步的

获取更新后的dom内容

class Vue {
	constructor(options) {
		...
	}
	$nextTick(callback) {
		return Promise.resolve().then(() => {
			callback()
		})
	}
}

如在created需要操作dom时,或需要立即获取数据变化后更新的dom,将相关逻辑callback写入$nextTick中

24、refs,refs, el, $data

$refs 获取dom

$el 获取当前组件根节点

$data 获取当前组件data数据

25、data定义数据

数据定义在data(return {}) return内和return外的区别

return外,单纯修改这个数据不可以修改,因为没有get/set,没有observer监听

return内,可以修改

26、computed的计算结果可以修改吗/组件v-model的值绑定computed来的,可以修改吗?

可以修改,但是需要通过get/set写法

computed: {
    // 只有get的写法
    // changeStr() {
    //   return xxx
    // }
    changeStr: {
        get() {
            return xxx
        },
        set(val) {
            this.xxx = val
        }
    }
}
27、watch
watch:{
    str(new, old){console.log(new, old)}
    num: {
        handle(new, old){console.log(new, old)},
        immediate: true  //立即执行
    }
    obj: {
        handle(new, old){console.log(new, old)},
        deep: true  //深度监听
    }
}
28、static和assets的区别

assets打包时走webpack打包流程(压缩等),static打包时直接复制

static中放第三方资源文件(一般经过处理),assets放自己项目中的资源文件

29、diff算法

功能: 提升性能

虚拟dom => 数据,dom数据化 主流:snabbdom、virtual-dom

真实节点

<h2>你好</h2>

虚拟节点

{
    children: undefined  // 子节点
    data: {} 
    elm: h2 // element
    key: undefined
    sel: "h2"    // 节点标签名称
    text: '你好' // innerText
}

新老节点替换规则

  1. 如果新老节点不是同一节点名称,删除旧节点,创建插入新节点
  2. 只能同级比较,不能跨层比较

如果要提升性能,一定要加入key,key是唯一标识,在更改前后可以确定是否为同一节点

h函数,生成虚拟节点

patch函数,对比旧的虚拟节点(真是dom转换的虚拟节点)和新的虚拟节点

// diff核心(最复杂的情况)  按照以下执行顺序判断
// https://www.bilibili.com/video/BV12W4y1x7Tb?p=34&vd_source=7a865b25661d91d1b1b2229b251eacd1
let vnode1 = h('div', {}, [
    h('span', {}, 'a'),   // <-对比时前指针
    h('span', {}, 'b'),
    h('span', {}, 'c'),
    h('span', {}, 'd'),   // <-对比时后指针
])

let vnode2 = h('div', {}, [
    h('span', {}, 'a'),   // <-对比时前指针
    h('span', {}, 'b'),
    h('span', {}, 'c'),
    h('span', {}, 'd'),   // <-对比时后指针
])
// 1.旧前 和 新前   匹配:旧前指针++ 新前指针++	 不匹配:↓
// 2.旧后 和 新后   匹配:旧后指针-- 新后指针--	 不匹配:↓	
// 3.旧前 和 新后   匹配:旧前指针++ 新后指针--	 不匹配:↓
// 4.旧后 和 新前   匹配:旧后指针-- 新前指针++	 不匹配:↓	
// 5.以上都不满足 ==> 查找 匹配:新匹配前节点添加到页面,新匹配前节点在旧可以匹配,旧节点设为undefined,新指针++。匹配到undefined指针++
// 6.创建或删除
30、为什么说Vue是渐进式框架,怎么理解的

渐进式:不需要立即使用整个框架,可以只使用一些功能,根据自己的需求和特点逐步应用框架的功能

比如Vue-router, vuex等,根据需求添加功能使用

31、Vue中有哪些修饰符
  1. 事件修饰符

    .stop 阻止冒泡

    .prevent 阻止默认行为

    .capture 內部元素触发的事件先在此处理

    .self 只有event.target是当前元素时触发

    .once 事件只会触发一次

    .passive 立即触发默认行为

    .native 把当前元素作为原生标签看待

  2. 按键修饰符

    .keyup 按键抬起

    .keydown 按键按下

  3. 系统修饰符

    .ctrl

    .alt

    .meta

  4. 鼠标修饰符

    .left

    .right

    .middle

  5. 表单修饰符

    .lazy 等输入完之后再显示

    .trim 删除内容前后空格

    .number 输入数字或转为数字

32、vue解决seo的方案

在使用Vue开发时,由于SPA单页面工程的特点,最终只会输出一个html文件,不利于搜索引擎优化,需要针对Vue进行seo优化

  1. 预渲染

    原理:利用库prerender-spa-plugin、vue-meta-info 配置webpack和页面实现预渲染的方案,最终build出来多个html文件

    优点:较容易实现,改动较小,较为可控

    缺点:不支持动态路由,install和build的时间较长

    注意:必须是history模式

  2. SSR服务器端渲染

    Vue项目可以使用Nuxt.js实现

33、vue的代码复用(logic reuse)

Vue.js中主要通过4种方式实现代码逻辑复用:

  • mixin
  • 高阶组件
  • 作用域插槽(scoped slots)
  • Composition API 组合式函数

4种方案带你探索 Vue.js 代码复用的前世今生

VUE3

vue3
  • 2020年9月发布的正式版
  • Vue3支持大多数Vue2的特性
  • Vue3中设计了一套强大的组合api代替了Vue2的optionApi,复用性更强
  • 更好的支持TS
  • Vue3使用了Proxy配合Reflect 代替了 Vue2 的 Object.defineProperty() 实现数据的响应式(数据代理)
  • 重写了虚拟dom,速度更快
  • 新的组件:Fragment(片段) / Teleport(瞬移) / Suspense(不确定)
  • 设计了新的脚手架工具vite
1、isProxy, isReactive, isReadonly的用法

isProxy 检查一个对象是否时reactive,readonly,shallowReactive, shallowReadonly创建代理

isReactive 检查一个对象是否时reactive,shallowReactive创建代理

isReadonly 检查一个对象是否时readonly,shallowReadonly创建代理

2、Vue2 和 Vue3生命周期的区别

Vue3生命周期完整指南

  1. vue3组合式api(composition api) 中生命周期少了beforeCreate,created,在setup中完成该2个生命周期要做的事情

  2. 所有组合式生命周期命名都在vue2生命周期的基础上加了“on”,比如vue2中beforeMount在组合式中为“onBeforeMount”,以此类推。选项式api不变

  3. vue2中生命周期在销毁阶段的生命周期分别是beforeDestory和destroyed,vue3中无论是组合式还是选项式命名都改成了beforeUnmount以及unmounted

  4. 在组合式生命周期中,传递一个箭头函数作为参数

    onBeforeMount(()=>{                      console.log("onBeforeMount")
    });
    

    因为箭头函数没有this,所以在函数体内不能通过this.elthis.el,this.data访问实例和数据,vue3提供了一个方法getCurrentInstance,通过引用该方法来访问实例

    import { getCurrentInstance } from "vue";
    ​
    const instance = getCurrentInstance()
                console.log(instance.data)
    
  5. 在vue3的任何选项式生命周期中,都可以获取到setup返回的响应式值。

  6. vue3组合式生命周期中,每一个相同的生命周期都比选项式生命周期先执行

  7. 在组合式生命周期中,除了setup内(对应是beforeCreate,created,访问不到实例),其他的生命周期都能访问到data.

  8. 总结

    <template>
        <div>
            <h1>{{user.age}}</h1>
            <h1>{{user.mes}}</h1>
            <h1>{{color}}</h1>
            <button @click="add()">加一</button>
        </div>
    </template>
     
    <script>
        import { computed, ref, reactive,onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted, onActivated, onDeactivated, onErrorCaptured,getCurrentInstance } from "vue";
        export default{
            name:'liveing',
            data(){
                return {
                  color:"blue"
                }
            },
            setup(){
                
                onBeforeMount(()=>{
                    console.log("onBeforeMount",instance.data,user)
                });
                onMounted(()=>{
                    console.log('onMounted',instance.data,user)
                });
                onBeforeUpdate(()=>{
                    console.log("onBeforeUpdate",instance.data,user)
                });
                onUpdated(()=>{
                    console.log("onUpdated",instance.data,user)
                });
                onBeforeUnmount(()=>{
                    console.log("onBeforeUnmounted",instance.data,user)
                });
                onUnmounted(()=>{
                    console.log("onUnmounted",instance.data,user)
                })
                const user=reactive({
                    age:1,
                    mes:"hello vue3.js"
                })
                let add=function(){
                    user.age++;
                }
                const instance = getCurrentInstance()
                // 这里打印出来是 {},因为 setup 声明周期是 beforeCreated 和 created 合并的,这时候 data 还没有初始化,所以我们要在 onMounted 里打印。
                console.log(instance.data)
                return {user,add}
            },
            beforeCreate() {
                console.log(this.$el,this.$data,this.user)
            },
            created() {
                console.log(this.$el,this.$data,this.user)
            },
            beforeMount() {
                console.log(this.$el,this.$data,this.user)
            },
            mounted() {
                console.log(this.$el,this.$data,this.user)
            },
            beforeUpdate() {
                console.log(this.$el,this.$data,this.user)
            },
            updated() {
                console.log(this.$el,this.$data,this.user)
            },
            beforeUnmount() {
                console.log(this.$el,this.$data,this.user)
            },
            unmounted() {
                console.log(this.$el,this.$data,this.user)
            }
            
        }
    </script>
     
    <style>
    </style>
    
3、选项式api (option api) 和 组合式api (composition api)的区别
Option API —— 又称选项API(vue2.x)

以vue为后缀的文件,通过定义methods,computed,watch,data等页面语法,共同处理页面逻辑

缺点:

  1. 随着组件功能的增大,关联性大大降低,阅读难
  2. 逻辑过多时this会出现指向不明等问题
  3. 代码出错时,自动检测和类型检查是失效的,因为本身不是有效的js代码
composition API —— 组合API(vue3.x)

根据逻辑功能来组织的,一个功能多定义的所有API会放在一起(高聚合、低耦合)

优点:

  1. 利于代码重用,没有对this的使用
  2. 不受模板和组件范围的限制,可以准确的知道我们可以使用哪些属性
  3. 几乎是函数,编辑器可以帮我们进行类型检查和建议
  4. 代码可读性更高,后期维护性也更高
4、setup执行时机问题

setup在beforeCreated前就调用且只调用一次(setup执行时组件还没创建出来,this是undefined,不能通过this调用data,methods,props,computed)

5、ref和reactive的细节
  • 是Vue3的 composition API中2个最重要的响应式API
  • ref用来处理基本类型数据, reactive用来处理对象(递归深度响应式)
  • 如果用ref对象/数组, 内部会自动将对象/数组转换为reactive的代理对象
  • ref内部: 通过给value属性添加getter/setter来实现对数据的劫持
  • reactive内部: 通过使用Proxy来实现对对象内部所有数据的劫持, 并通过Reflect操作对象内部数据
  • ref的数据操作: 在js中要.value, 在模板中不需要(内部解析模板时会自动添加.value)

对数组进行响应式声明时选择ref:1、模板更简洁 {{refObj}} 2、与其他对象解耦

6、customRef防抖使用
<template>
  <h2>App</h2>
  <input v-model="keyword" placeholder="搜索关键字"/>
  <p>{{keyword}}</p>
</template><script lang="ts">
/*
customRef:
  创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制
​
需求: 
  使用 customRef 实现 debounce 的示例
*/import {
  ref,
  customRef
} from 'vue'export default {
​
  setup () {
    const keyword = useDebouncedRef('', 500)
    console.log(keyword)
    return {
      keyword
    }
  },
}
​
/* 
实现函数防抖的自定义ref
*/
function useDebouncedRef<T>(value: T, delay = 200) {
  let timeout: number
  return customRef((track, trigger) => {
    return {
      get() {
        // 告诉Vue追踪数据
        track()
        return value
      },
      set(newValue: T) {
        clearTimeout(timeout)
        timeout = setTimeout(() => {
          value = newValue
          // 告诉Vue去触发界面更新
          trigger()
        }, delay)
      }
    }
  })
}
​
</script>
7、Vue3使用了新的全局状态管理器pinia, 与vuex有哪些区别

vuex有state、getters、mutations(同步操作)、actions(异步操作)、modules

pinia只有state、getters、actions(可做同步和异步操作)

8、watch 和 watchEffect
9、watch做深度监听
10、使用Ref还是Reactive

使用Ref还是Reactive?

使用ref时到处使用.value是很麻烦的,但当你用reactive创建的响应式对象进行重构时,也很容易丢失响应性。一句话总结:默认情况下使用ref,当你需要对变量分组时使用reactive

Vue3的响应式: Vue框架是通过拦截对象属性的读写实现响应式,Vue 2专门使用getters/setters来拦截属性。Vue 3对响应式对象使用Proxy,对ref使用getters/setters。

//属性拦截的基本原理伪代码
function reactive(obj) {
  return new Proxy(obj, {
    get(target, key) {
      track(target, key)
      return target[key]
    },
    set(target, key, value) {
      target[key] = value
      trigger(target, key)
    },
  })
}

reactive()

默认是深度响应式的。如果你修改了嵌套的数组或对象,这些更改都会被vue检测到

import { reactive } from 'vue'const state = reactive({
  count: 0,
  nested: { count: 0 },
})
​
watch(state, () => console.log(state))
// "{ count: 0, nested: { count: 0 } }"const incrementNestedCount = () => {
  state.nested.count += 1
  // Triggers watcher -> "{ count: 0, nested: { count: 1 } }"
}

reactive()API有两个限制:

  • 它只适用于对象类型,比如对象、数组和集合类型,如MapSet。它不适用于原始类型,比如stringnumberboolean

  • reactive()返回的代理对象与原始对象是不一样的。用===操作符进行比较会返回false

    const plainJsObject = {}
    const proxy = reactive(plainJsObject)
    ​
    // proxy is NOT equal to the original plain JS object.
    console.log(proxy === plainJsObject) // false
    

ref()

Vue提供了ref()函数来解决reactive()的限制,ref()并不局限对象类型,可以容纳任何值类型。读写通过ref()创建的响应式变量,你需要通过.value属性来访问

//ref()简化逻辑
function ref(value) {
  const refObject = {
    get value() {
      track(refObject, 'value')
      return value
    },
    set value(newValue) {
      value = newValue
      trigger(refObject, 'value')
    },
  }
  return refObject
}
​
//当拥有对象类型时,ref自动用reactive()转换其.value
//ref({}) ~= ref(reactive({}))
reactiveref
👎 只对对象类型起作用👍对任何类型起作用
👍在<script><template>中访问值没有区别👎访问<script><template>中的值的行为不同
👎重新赋值一个新的对象会"断开"响应式👍对象引用可以被重新赋值
属性可以在没有.value的情况下被访问需要使用.value来访问属性
👍引用可以通过函数进行传递
👎解构的值不是响应式的
👍与Vue2的data对象相似
11、Ref原理

实现响应式数据reactive(只能接受对象、数组等复杂类型对象)是通过proxy实现的,proxy不能接收non-object数据,因此需要ref处理一般数据类型的数据

ref需要.value <= proxy不能接收non-object数据,在.value中模拟proxy实现get()、set()操作实现响应式

ref接收数据会进行判断是否为object,非object类型数据使用ref方式处理,object类型的数据调用toReactive函数进行Reactive处理。

HTML、CSS

1、<img>titlealt有什么区别

1、title是全局属性之一,用于为元素提供附加的advisory information。通常当鼠标滑动到元素上的时候显示。

2、alt是的特有属性,是图片内容的等价描述,用于图片无法加载时显示、读屛器阅读图片。可提高图片的访问性,除了纯装饰图片外都必须设置有意义的值,搜索引擎会重点分析

2、html5有哪些新特性、移除了那些元素

H5新特性主要包括: 语意化更好的内容元素,比如article、footer、header、nav、section 表单控件,calendar、date、time、email、url、search canvas 画布 audio, video drag 拖拽 本地存储zhi localStorage, sessionStorage webSocket 长连dao接 定位 增强型表zhuan单 input number, datalist, keygen, output, progress svg 矢量绘图 webWorker 实现shujs多进程。 移除的元素: 纯表现的元素:basefont,big,center,font, s,strike,tt,u` 对可用性产生负面影响的元素:frame,frameset,noframes

3、行内元素有哪些?块级元素有哪些? 空(void)元素有那些?行内元素和块级元素有什么区别?

行内元素:span img input

块级元素:div footer header section p h1... h6...

空元素:br hr...

4、HTML全局属性(global attribute)有哪些
5、display: none;visibility: hidden;的区别
  • 作用不同:

    visibility:hidden将元素隐藏,但是在网页中该占的位置还是占着。

    display:none将元素的显示设为无,即在网页中不占任何的位置。

  • 使用后HTML元素有所不同:

    visibility:hidden,使用该属性后,HTML元素(对象)仅仅是在视觉上看不见(完全透明),而它所占据的空间位置仍然存在,也即是说它仍具有高度、宽度等属性值。

    display:none,使用该属性后,HTML元素(对象)的宽度、高度等各种属性值都将“丢失”。

  • 定义不同:

    visibility属性指定一个元素是否是可见的。

    display这个属性用于定义建立布局时元素生成的显示框类型。

6、link@import的区别
  • 先有的link,link的兼容性比@import好
  • 加载顺序差别,浏览器先加载标签link,后加载@import
7、display,float,position的关系
8、css3有哪些新特性?CSS3新增伪类有那些?
9、href和src的区别
  1. 作用结果不同

    href 用于在当前文档和引用资源之间确立联系

    src 用于替换当前内容

  2. 浏览器解析方式不同

    若在文档中添加href ,浏览器会识别该文档为 CSS 文件,就会并行下载资源并且不会停止对当前文档的处理。这也是为什么建议使用 link 方式加载 CSS,而不是使用 @import 方式。

    当浏览器解析到src ,会暂停其他资源的下载和处理,直到将该资源加载、编译、执行完毕,图片和框架等也如此,类似于将所指向资源应用到当前内容。这也是为什么建议把 js 脚本放在底部而不是头部的原因

  3. 请求资源类型不同

    href是Hypertext Reference的缩写,表示超文本引用。用来建立当前元素和文档之间的链接。常用的有:link、a。

    在请求 src 资源时会将其指向的资源下载并应用到文档中,常用的有script,img 、iframe。

10、盒模型

CSS3有标准盒模型和IE盒模型

盒模型由content+padding+margin+border构成

标准盒模型width/height只包含content

IE盒模型width/height包含content+padding+border

可以通过box-sizing改变元素的盒模型

  • box-sizing: content-box :标准盒模型(默认值)。
  • box-sizing: border-box :IE(替代)盒模型。
11、css选择器优先级

优先级 !important > style > id > class > < />

12、对BFC的理解

回答参考:

  1. 什么是BFC,其规则是什么?

    Block formatting context (块级格式上下文) 是一个独立的渲染区域,可以理解为一个封闭容器,內部元素不会影响到外部,容器内的样式布局也不会受外界影响。內部规则:BFC是一个块级元素,在垂直方向一个接一个排列。BFC是一个隔离的独立容器,容器里的标签不会影响外部标签。BFC区域不会与浮动的容器发生重叠。属于同一个BFC的两个相邻元素的外边距会发生重叠。计算BFC高度时浮动元素也会参与计算

  2. 怎么触发BFC ?

    • 绝对定位元素(positionabsolutefixed )。
    • 行内块元素,即 displayinline-block 。display的值为inline-block、inline-flex、flex、flow-root、table-caption、table-cell
    • overflow 的值不为 visiblehidden or auto
    • float的值不为none
  3. BFC能解决什么问题 ?

    • 阻止元素被浮动元素覆盖
    • 解决父元素没有高度,子元素设置为浮动元素时的高度塌陷问题
    • 解决margin边距重叠问题

BFC 即块级格式上下文,根据盒模型可知,每个元素都被定义为一个矩形盒子,然而盒子的布局会受到尺寸,定位,盒子的子元素或兄弟元素,视口的尺寸等因素决定,所以这里有一个浏览器计算的过程,计算的规则就是由一个叫做视觉格式化模型的东西所定义的,BFC 就是来自这个概念,它是 CSS 视觉渲染的一部分,用于决定块级盒的布局及浮动相互影响范围的一个区域

BFC 具有一些特性:

  1. 块级元素会在垂直方向一个接一个的排列,和文档流的排列方式一致。

  2. 在 BFC 中上下相邻的两个容器的 margin 会重叠,创建新的 BFC 可以避免外边距重叠。

  3. 计算 BFC 的高度时,需要计算浮动元素的高度。

  4. BFC 区域不会与浮动的容器发生重叠。

  5. BFC 是独立的容器,容器内部元素不会影响外部元素。

  6. 每个元素的左 margin 值和容器的左 border 相接触。

    利用这些特性,我们可以解决以下问题:

    • 利用 46 ,我们可以实现三栏(或两栏)自适应布局。
    • 利用 2 ,我们可以避免 margin 重叠问题。
    • 利用 3 ,我们可以避免高度塌陷。

    创建 BFC 的方式:

    • 绝对定位元素(positionabsolutefixed )。
    • 行内块元素,即 displayinline-block 。display的值为inline-block、inline-flex、flex、flow-root、table-caption、table-cell
    • overflow 的值不为 visible
    • float的值不为none

可能式最好的BFC解析了

13、实现两栏布局(左侧固定 + 右侧自适应布局)
<div class="outer">
  <div class="left">左侧</div>
  <div class="right">右侧</div>
</div>
<style>
/**利用浮动,左边元素宽度固定 ,设置向左浮动。将右边元素的 margin-left 设为固定宽度 。注意,因为右边元素的 width 默认为 auto ,所以会自动撑满父元素。*/    
.outer {
  height: 100px;
}
.left {
  float: left;
  width: 200px;
  height: 100%;
  background: lightcoral;
}
.right {
  margin-left: 200px;
  height: 100%;
  background: lightseagreen;
}
</style>

左侧

右侧

左侧

右侧

左侧

右侧

左侧

右侧

14、实现圣杯布局和双飞翼布局(经典三栏布局)

圣杯布局和双飞翼布局的目的:

  • 三栏布局,中间一栏最先加载和渲染(内容最重要,这就是为什么还需要了解这种布局的原因)。
  • 两侧内容固定,中间内容随着宽度自适应。
  • 一般用于 PC 网页。

圣杯布局和双飞翼布局的技术总结:

  • 使用 float 布局。
  • 两侧使用 margin 负值,以便和中间内容横向重叠。
  • 防止中间内容被两侧覆盖,圣杯布局用 padding ,双飞翼布局用 margin

圣杯布局

<div id="container" class="clearfix">
  <p class="center">我是中间</p>
  <p class="left">我是左边</p>
  <p class="right">我是右边</p>
</div>
<style>
	#container {
  padding-left: 200px;
  padding-right: 150px;
  overflow: auto;
}
#container p {
  float: left;
}
.center {
  width: 100%;
  background-color: lightcoral;
}
.left {
  width: 200px;
  position: relative;
  left: -200px;
  margin-left: -100%;
  background-color: lightcyan;
}
.right {
  width: 150px;
  margin-right: -150px;
  background-color: lightgreen;
}
.clearfix:after {
  content: "";
  display: table;
  clear: both;
}
</style>

双飞翼布局

main

left

right

15、水平垂直居中的多种实现方式
  1. 利用绝对定位,设置 left: 50%top: 50% 现将子元素左上角移到父元素中心位置,然后再通过 translate 来调整子元素的中心点到父元素的中心。该方法可以不定宽高

    .father {
      position: relative;
    }
    .son {
      position: absolute;
      left: 50%;
      top: 50%;
      transform: translate(-50%, -50%);
    }
    
  2. 利用绝对定位,子元素所有方向都为 0 ,将 margin 设置为 auto ,由于宽高固定,对应方向实现平分,该方法必须盒子有宽高

    .father {
      position: relative;
    }
    .son {
      position: absolute;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0px;
      margin: auto;
      height: 100px;
      width: 100px;
    }
    
  3. 利用绝对定位,设置 left: 50%top: 50% 现将子元素左上角移到父元素中心位置,然后再通过 margin-leftmargin-top 以子元素自己的一半宽高进行负值赋值。该方法必须定宽高

.father {
  position: relative;
}
.son {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 200px;
  height: 200px;
  margin-left: -100px;
  margin-top: -100px;
}

4.利用 flex ,最经典最方便的一种了,不用解释,定不定宽高无所谓的

.father {
  display: flex;
  justify-content: center;
  align-items: center;
}
15、css哪些方式可以隐藏页面元素
  • display:none

  • visibility:hidden

  • opacity:0

  • 设置height、width模型属性为0

  • position:absolute

  • clip-path

    image-20221202200411473

16、用CSS实现三角符号
div:after{
    position: absolute;
    width:0px;
    height:0px;
    content:'';
    border-right:100px soild transparent;
    border-top: 100px solid #ff0;
    border-left: 100px soild transparent;
    border-bottom: 100px soild transparent;
}
17、过渡transition和动画animation的区别
  1. transition是一个过渡的效果,没有中间状态,需要设置触发事件(如hover 或 js事件 等)才能执行;
  2. transition 只能触发一次
  3. animation是一个动画的效果,有多个中间帧,animation 配合 @keyframe 可以不触发时间就触发这个过程。
  4. animation 可以设置很多的属性,比如循环次数,动画结束的状态等等
18、height和line-height的区别

line-height是每一行文字的高,如果文字换行会把整个盒子高度撑大(行数 * 行高)

height是盒子的高度,不会变化

19、不用border创建边框

box-shadow: x轴偏移 y轴偏移 (模糊半径 扩散半径) 颜色

box-shadow: 0 0 0 5px #fafafa
20、css选择器 ~ (波浪号)、+(加号)、>(大于号)的用法解析和举例

~ (波浪号):A ~ B表示选择A标签后的所有B标签,但是A和B标签必须有相同的父元素

> (大于号): 表示某个元素的下一代元素。A>B指选择A元素里面的B元素,其中B元素是A元素的第一代。A B{}是A元素的所有B元素后代

+ (加号) A+B{}这个+是选择相邻兄弟,称作“相邻兄弟选择器”,如果需要选择紧接在另一个元素后的元素,而且二者有相同的父元素,可以使用相邻兄弟选择器

21、边界塌陷

高度塌陷以及解决方法

CSS 深入理解:overflow: hidden——隐藏溢出、清除浮动、解除坍塌

22、margin上下重叠bug

margin上下重叠bug

23、margin写法

margin跟padding一样,也有简洁写法。我们可以使用margin属性来设置四个方向的外边距。在实际编程中,我们往往使用的是margin的这种高效简洁写法来编程。

margin写法有4种,分别如下:

 margin: 像素值1;
 margin: 像素值1 像素值2;
 margin: 像素值1 像素值2 像素值3; 
 margin: 像素值1 像素值2 像素值3 像素值4;

以上四个位置按顺序分别为:margin-top--margin-right--margin-bottom--margin-left,即“---”。以下简写为top--right--bottom--left。其中需要注意的是后三种情况,当有像素值缺省时,浏览器会自动对缺省像素按照“bottom=top”和“left=right”的方法进行赋值。

例如:

“margin:20px;”表示四个方向的外边距都是20px;

“margin:20px 40px;”表示top为20px,right为40px;由于bottom和left缺省,所以自动将它们分别设为20px和40px。转化为第4种写法为:“margin:20px 40px 20px 40px;”。

“margin:20px 40px 60px;”表示top为20px,right为40px,bottom为60px;由于left缺省,所以自动将它设为40px。转化为第4种写法为:“margin:20px 40px 60px 40px;”。

需要注意的是一种情况不能写为缺省写法:“margin:20px 40px 20px 60px;”。该例中,由于top和bottom相同,但right和left不同,所以不能将bottom缺省,否则会等同于“margin:20px 40px 60px 40px;”。

24、说一下浮动

牛客回答链接

得分点

脱离文档流、盒子塌陷、 影响其他元素排版、伪元素 、overflow:hidden 、标签插入法

参考答案

标准回答

浮动的作用,设置浮动的图片,可以实现文字环绕图片,设置了浮动的块级元素可以排列在同一行,设置了浮动的行内元素可以设置宽高,同时可以按照浮动设置的方向对齐排列盒子。

设置浮动元素的特点:

  • 设置了浮动,该元素脱标。元素不占位置
  • 浮动可以进行模式转换(行内块元素)

浮动造成的影响,使盒子脱离文档流,如果父级盒子没有设置高度,需要被子盒子撑开,那么这时候父级盒子的高度就塌陷了,同时也会造成父级盒子后面的兄弟盒子布局受到影响。如果浮动元素后面还有其他兄弟元素,其他兄弟元素的布局也会受到影响。

25、你知道flex: 1;的更深层次的内容吗?
  • flex:1实际代表的是三个属性的简写
  • flex-grow是用来增大盒子的,比如,当父盒子的宽度大于子盒子的宽度,父盒子的剩余空间可以利用flex-grow来设置子盒子增大的占比
  • flex-shrink用来设置子盒子超过父盒子的宽度后,超出部分进行缩小的取值比例
  • flex-basis是用来设置盒子的基准宽度,并且basis和width同时存在basis会把width干掉

所以flex:1;的逻辑就是用flex-basis把width干掉,然后再用flex-grow和flex-shrink增大的增大缩小的缩小,达成最终的效果。

26、伪类和伪元素
  1. 伪类和伪元素都是用来表示文档树以外的"元素"
  2. 伪类和伪元素分别用单冒号: 和 双冒号 :: 表示
  3. 创建了新的元素的为伪元素
  4. 可以同时使用多个的是伪类,只能同时使用一个的是伪元素
  5. 类选择器和伪类选择器一样的优先级,因此伪类选择器 > 伪元素选择器
27、position定位理解
absolute生成绝对定位的元素,相对于 static 定位以外的第一个父元素进行定位。元素的位置通过 "left", "top", "right" 以及 "bottom" 属性进行规定。
fixed生成固定定位的元素,相对于浏览器窗口进行定位。元素的位置通过 "left", "top", "right" 以及 "bottom" 属性进行规定。
relative生成相对定位的元素,相对于其正常位置进行定位。因此,"left:20" 会向元素的 LEFT 位置添加 20 像素。
static默认值。没有定位,元素出现在正常的流中(忽略 top, bottom, left, right 或者 z-index 声明)。
sticky粘性定位,该定位基于用户滚动的位置。它的行为就像 position:relative; 而当页面滚动超出目标区域时,它的表现就像 position:fixed;,它会固定在目标位置。注意: Internet Explorer, Edge 15 及更早 IE 版本不支持 sticky 定位。 Safari 需要使用 -webkit- prefix (查看以下实例)。
inherit规定应该从父元素继承 position 属性的值。
initial设置该属性为默认值,详情查看 CSS initial 关键字
28、解决了什么移动端的兼容问题
  1. 当设置样式overflow: scroll / auto时,IOS上滑动会卡顿

    -webkit-overflow-scrolling: touch

  2. 在安卓环境下placeholder文字设置行高时会偏上

    input有placeholder属性不要设置行高

  3. 移动端字体小于12px时异常显示

    应该先把整体放大一倍,再用transform进行缩小

  4. IOS下input按钮设置了disabled为true时显示异常

    input[type = button] {

    opcity: 1

    }

  5. 安卓手机下取消语音输入按钮

    input::-webkit-input-speech-button{

    display: none

    }

  6. IOS下取消input输入框在输入英文时首字母默认大写

  7. 禁用IOS和安卓用户选中文字

    添加全局CSS样式:-webkit-user-select: none

  8. 禁用IOS弹出各种窗口

    -webkit-touch-callout: none

  9. 禁用IOS识别长串数字为电话

    添加meta属性

29、svg格式

基于XML语法格式的图像格式,可缩放矢量图。其他图像基于像素,svg属于对图像形状的描述,本质是文本文件,体积小,缩放不会失真

  1. svg可以直接插入页面中,成为DOM的一部分,用JS或CSS进行一些操作

    <svg></svg>
    
  2. svg可作为文件被引入

    <img src='pic.svg'/>
    
  3. svg可转为base64格式

其他

1、语义化的理解

语义化是指 用合理的HTML标记以及其特有的属性去格式化文档内容。如,标题用h1-h6, 段落用p标签,合理得给图片添加alt属性(alt属性是一个用于网页语言HTML和XHTML、为输出纯文字的参数属性,作用是当HTML元素本身的物件无法被渲染时,就显示alt(替换)文字作为一种补救措施。)

语义化的好处:

  • 在没有CSS的情况下,页面也能呈现出很好的内容结构
  • 语义化使代码更具可读性,便于团队开发和维护
  • 语义化有利于用户体验(例如 title,label,alt属性的灵活运用)
  • 语义化有利于SEO(和搜索引擎建立良好的沟通,有助于爬虫爬更多的有效信息。爬虫依赖于标签来确定上下文和各个关键字的权重)
2、iframe有那些缺点
3、Canvas和SVG有什么区别?

SVG与canvas的区别

(1)SVG是用来描述XML中2D图形的语言,canvas借助JavaScript动态描绘2D图形

(2)SVG可支持事件处理程序而canvas不支持

(3)SVG中属性改变时,浏览器可以重新呈现它,适用于矢量图,而canvas不可以,更适合视频游戏等。

(4)canvas可以很好的绘制像素,用于保存结果为png或者gif,可做为API容器。

(5)canvas取决于分辨率。SVG与分辨率无关。

(6)SVG具有更好的文本渲染,而Canvas不能很好的渲染,渲染中的SVG可能比Canvas慢,特别是应用了大量的DOM。

(7)画布更适合渲染较小的区域。SVG渲染更好的更大区域。

4、HTML5 为什么只需要写 ?
5、清除浮动的几种方式,各自的优缺点
  • 使用clear属性的空元素

    在浮动元素后使用一个空元素

    ,并在CSS中赋予.clear{clear:both;}属性即可清理浮动。亦可使用

    来进行清理。 使用这种方式清除浮动优点是简单,兼容性也好 ;缺点是不利于代码语义化,后期维护成本大。

  • 使用css的overflow属性

    给浮动元素的容器添加overflow:hidden;或overflow:auto;可以清除浮动,另外在 IE6 中还需要触发 hasLayout 。为父元素设置容器宽高或设置zoom:1。在添加overflow属性后,浮动元素又回到了容器层,把容器高度撑起,达到了清理浮动的效果。

    使用这种方式清除浮动优点是简单,浏览器支持好; 缺点是不能和position配合使用,因为超出的尺寸会被overflow:hidden隐藏。

  • 使用CSS的:after伪元素

    结合 :after 伪元素,伪元素,表示一个元素之后最近的元素和 IEhack ,可以完美兼容当前主流浏览器。其中, IEhack 指的是触发hasLayout。需要注意的是为了IE6和IE7浏览器,要给clearfix这个class添加一条zoom:1;触发haslayout。

    给浮动元素的容器添加一个 clearfix 的class,然后给这个class添加一个:after伪元素实现元素末尾添加一个看不见的块元素(Block element)清理浮动。通过CSS伪元素在容器的内部元素最后添加了一个看不见的空格“020”或点“.”,并且赋予clear属性来清除浮动。

    使用这种方式清除浮动的优点是 浏览器支持好,不容易出现怪问题,在大型网站中都有使用,如腾迅,网易,新浪等等); 缺点是代码多,要两句代码结合使用,才能让主流浏览器都支持。

  • 给父级元素设置高度

    使用这种方式清除浮动的优点是简单, 代码少,好掌握 ;缺点是只适用于高度固定的布局。

6、说一下SPA单页面应用的优缺点
  • 切换页面速度快,用户体验好,请求量小,前后端分离清晰
  • 首屏加载慢,初次加载资源量大,不利于seo优化
7、设计模式

9种前端常见的设计模式

SOLID五大设计原则

53道常见NodeJS基础面试题(附答案)

node.js面试题

为什么需要虚拟DOM

非提高效率 React.fiber Svelte 不需要虚拟dom

1、框架设计层面

数据驱动(vue、react等) 数据变动 render函数使组件界面全量生成 使用虚拟dom减少全量生成的次数减少开销

Svelte 在编译时就知道数据对应的真实dom,直接改动真实dom

说几种页面刷新方式

强制刷新,页面出现空白location.reload()、this.router.go(0)普通刷新,provide/inject(provide传递函数控制vif销毁重建组件刷新)this.router.go(0) 普通刷新,provide/inject(provide传递函数控制v-if销毁重建组件刷新) this.FORCEUPDATE

Map和Set的区别,Map和Object的区别

Map和Set的区别

Map和Object

  1. Object的value必须是简单数据类型(整数、字符串、symbol),map的value可以是任何类型
  2. Map元素插入顺序是FIFO,object没有
  3. Map继承Object
  4. Map在存储大量元素的时候性能表现更好
  5. 写入删除密集的情况应该使用 Map

Map和Set

  1. Set以[value, value]的形式储存元素,Map以[key,value]的形式储存元素
  2. map的值不作为键,键和值是分开的

数组filter、every、 flat的作用

JS数组的filter, every, some, map, forEach, flat作用

ES6有哪些新特性

ES6十大新特性

前端工程化

构建工具:webpack、vite、rollup等

前端构建工具,用于自动化前端项目的构建

  • 工程源代码 -> 运行时代码(浏览器)
  • 支持插件扩展
  • 丰富的配置功能
  • 压缩、丑化、合并等优化功能

Bundless阶段(vite)

  • 开发阶段无需打包
  • 启动快
  • 热更新效果好
  • 构建阶段用Rollup打包

webpack可靠性强

Rollup适合类库型项目

常见的loader

  • file-loader: 把文件输出到一个文件夹中,在代码中通过相对URL去引用输出的文件
  • url-loader: 和file-loader类似,但是能在文件很小的情况以base64方式把文件内容注入代码中
  • source-map-loader: 加载额外的Source Map文件,以方便断点调试
  • image-loader: 加载并压缩图片文件
  • babel-loader: 将ES6转换为ES5
  • css-loader: 加载css,支持模块化、压缩、文件导入等特性
  • style-loader: 把css代码注入js中,通过DOM操作加载css
  • eslint-loader: 通过ESLint检查JavaScript代码

按一定维度回答:

  • JS

    • ES6 babel-loader
    • TS ts-loader
    • 代码规范 eslint-loader
    • vue vue-loader
  • CSS

    • sass、less 预处理器loader
    • style-loader、 css-loader
    • post-css-loader
  • 图片资源

    • file-loader
    • url-loader
    • image-loader

手写防抖和节流

防抖策略(debounce)是当事件被触发后,延迟n秒后再执行回调函数,如果在这n秒内事件被再次触发,则重新计时.

好处是: 它能够保证用户在频繁触发某些事件的时候,不会频繁的执行回调,只会被执行一次.

防抖的应用场景: 用户在输入框连续输入一串字符时,可以通过防抖策略,只在输入完后,才执行查询的请求,这样可以有效减少请求次数,节约请求资源.

节流策略(throttle),可以减少一段时间内事件的触发频率。 节流策略的应用场景: 鼠标不断触发某事件时,如点击,只在单位事件内触发一次。懒加载时要监听计算滚动条的位置,但不必要每次滑动都触发,可以降低计算频率,而不必要浪费CPU资源.

function debounce(fn, delay) {
    let timer = null;
    const _debounce = function (...args) {
        if (timer) clearTimeout(timer);
        timer = setTimeout(() => {
            fn.apply(this, args);
        }, delay)
    }
    return _debounce
}
//时间戳
function throttle(fn, delay) {
    let lastTime = 0;
    return function() {
        let nowTime = Date.now();
        if (delay < nowTime - lastTime) {
        	lastTime = nowTime;
            return fn.apply(this);
        }
    }
}

new Vue发生了什么

Vue实际上是一个类,类在JavaScript中使用Function来实现的

function Vue (options) {
  if (process.env.NODE_ENV !== 'production' &&
    !(this instanceof Vue)
  ) {
    warn('Vue is a constructor and should be called with the `new` keyword')
  }
  this._init(options)
}

可以看到 Vue 只能通过 new 关键字初始化,然后会调用 this._init 方法, 该方法在 src/core/instance/init.js 中定义

Vue 初始化主要就干了几件事情,合并配置,初始化生命周期,初始化事件中心,初始化渲染,初始化 data、props、computed、watcher 等等。

vue2和vue3的区别

blog.csdn.net/weixin_5472…

blog.csdn.net/weixin_4363…

Proxy与Object.defineProperty的优劣对比

Object.defineProperty 劫持数据 只是对对象的属性进行劫持 无法监听新增属性和删除属性 需要使用 vue.set, vue.delete 深层对象的劫持需要一次性递归 劫持数组时需要重写覆盖部分 Array.prototype 原生方法

Proxy 劫持数据 真正的对对象本身进行劫持 可以监听到对象新增删除属性 只在 getter 时才对对象的下一层进行劫持(优化了性能) 能正确监听原生数组方法 无法 polyfill 存在浏览器兼容问题

总结: Object.defineProperty 是对对象属性的劫持 Proxy 是对整个对象劫持

Object.defineProperty 无法监听新增和删除 Object.defineProperty 无法监听数组部分方法需要重写 Object.defineProperty 性能不好要对深层对象劫持要一次性递归

Proxy 能正确监听数组方法 Proxy 能正确监听对象新增删除属性 Proxy 只在 getter 时才进行对象下一层属性的劫持 性能优化 Proxy 兼容性不好

Object.defineProperty 和 Proxy 在 getter 时进行添加依赖 dep.addSub(watcher) 比如 绑定 view 层,在函数中使用 在 setter 时触发依赖通知 dep.notify() 比如 修改属性

Proxy的优势如下:

Proxy可以直接监听对象而非属性 Proxy可以直接监听数组的变化 Proxy有多达13种拦截方法,不限于apply、ownKeys、deleteProperty、has等等是Object.defineProperty不具备的 Proxy返回的是一个新对象,我们可以只操作新的对象达到目的,而Object.defineProperty只能遍历对象属性直接修改 Proxy作为新标准将受到浏览器厂商重点持续的性能优化,也就是传说中的新标准的性能红利

Object.defineProperty的优势如下:

兼容性好,支持IE9

flex三个属性

image-20221013225738497

手动封装Axios

Vue项目中Axios的简单封装

为什么v-if和v-for不建议一起用

Vue2中v-for比v-if优先级高,把他们放在一起,输出的渲染函数中可以看出先执行循环再判断条件,哪怕只渲染列表的一部分,也要在每次重渲染时遍历整个列表,造成浪费,影响性能。

Vue3中v-if比v-for优先级高,所以v-if执行时它所调用的变量还不存在,会导致异常

可以对循环的列表做filter过滤处理,返回过滤后的列表循环渲染

可以把v-if移动至容器元素上(包裹一层div、li)

箭头函数和普通函数的区别

箭头函数和普通函数区别

map和forEach的区别和应用场景

  1. map速度比foreach快
  2. map会返回一个新数组,不对原数组产生影响;foreach不会产生新数组,foreach返回undefined
  3. map因为返回数组所以可以链式操作,foreach不能
  4. map里可以用return ,而foreach里用return不起作用,foreach不能用break,会直接报错

forEach用于我们对数组的元素进行处理时(例如:增加元素,元素值改变)。

map适用于你要改变数据值的时候,不仅在于它更快,而且返回一个新的数组,这样可以提高复用性(map(),filter(),reduce())等组合使用。实战中例如整合数据按参数格式发给服务器

Get和Post的区别

  1. get是从服务器上获取数据,post是向服务器传送数据。
  2. GET请求把参数包含在URL中,将请求信息放在URL后面,POST请求通过request body传递参数,将请求信息放置在报文体中。
  3. get传送的数据量较小,不能大于2KB。post传送的数据量较大,一般被默认为不受限制。但理论上,IIS4中最大量为80KB,IIS5中为100KB
  4. get安全性非常低,get设计成传输数据,一般都在地址栏里面可以看到,post安全性较高,post传递数据比较隐私,所以在地址栏看不到, 如果没有加密,他们安全级别都是一样的,随便一个监听器都可以把所有的数据监听到。
  5. GET请求能够被缓存,GET请求会保存在浏览器的浏览记录中,以GET请求的URL能够保存为浏览器书签,post请求不具有这些功能。
  6. HTTP的底层是TCP/IP,GET和POST的底层也是TCP/IP,也就是说,GET/POST都是TCP链接。GET和POST能做的事情是一样一样的。你要给GET加上request body,给POST带上url参数,技术上是完全行的通的。
  7. GET产生一个TCP数据包,对于GET方式的请求,浏览器会把http header和data一并发送出去,服务器响应200(返回数据);POST产生两个TCP数据包,对于POST,浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok(返回数据),并不是所有浏览器都会在POST中发送两次包,Firefox就只发送一次

使用Vue中遇到的坑

display的属性值

display:none; 设置元素隐藏,具体可见:display:none display:block; 设置元素为块级元素,块级元素可以独占一行,可设宽高。 display:inline; 设置元素为行内元素,一行可有多个行内块元素,不可设宽高。 display:inline-block 设置元素为行内块元素,既有行内元素的(一行可有多个)特性,又有块元素的(可设宽高)特性 display:inline-table inline-table得到的是,外面是“内联盒子”,里面是“table盒子”。 display:table 元素会作为块级表格来显示,类似 table,表格前后带有换行符;配合table-cell使用可实现水平垂直居中,具体可见:水平垂直居中 table-row 元素会作为一个表格行显示,类似 tr; table-cell 元素会作为一个表格单元格显示,类似 td和 th。 display:list-item 为元素内容生成一个块型盒,随后再生成一个列表型的行内盒。 会把元素作为列表显示,要完全模仿列表的话还需要加上 list-style-position,list-style-type

inline和block的区别

总体概念

1、block 块级元素,独立一块,会换行 inline 内联元素,前后不会换行,一系列内联元素在一行显示,直到排满 2、常见的块级元素:div,p,table,h1~h6,ul,li,ol 常见的内联元素:span ,img ,input ,lable,select,a 3、block可以包含block,inline,inline只能包含inline.注:P元素只能包含inline不能包含block

细节对比

block: 1、默认情况下,block宽度会填满父容器的宽度。 2、block可以设置高宽。 3、block可以设置padding,margin inline: 1、多个相邻的行内元素排在一行,直到一列排不下才会换行,其宽度随元素的内容而变化。 2、inline设置高宽无效。 3、inline元素水平方向的padding,margin产生边距效果,竖直方向不会产生边距效果。 inline-block 将对象呈现为inline对象,但是对象的内容作为bloack对象呈现。以后的内联对象会排列在同一行。

Hash和history模式

  • hash模式所有的工作都是在前端完成的,不需要后端服务的配合,兼容性好
  • hash模式的实现方式就是通过监听URL中hash部分的变化,从而做出对应的渲染逻辑
  • hash模式下,URL中会带有#,看起来不太美观
  • history.pushState和history.replaceState方法是不会触发popstate事件的
  • 但是浏览器的某些行为会导致popstate,比如go、back、forward
  • popstate事件对象中的state属性,可以理解是我们在通过history.pushState或history.replaceState方法时,传入的指定的数据

可答

vue-router有3个模式, Abstract 模式:支持所有 javascript 运行模式,如果发现没有浏览器的API,路由会自动强制进入这个模式。其中history和hash更为常用。两者差别主要在显示形式、seo和部署上。

hash模式在地址栏显示的时候是已哈希的形式:#/xxx,这种方式使用和部署简单,但是不会被搜索引擎处理,seo有问题;history模式则建议用在大部分web项目上,但是它要求应用在部署时做特殊配置,服务器需要做回退处理,否则会出现刷新页面404的问题。

底层实现上其实hash是一种特殊的history实现。

从浏览器地址栏输入url到请求返回发生了什么

  1. 输入url后解析出协议、主机、端口、路径等信息,并构造一个http请求
  2. DNS域名解析
  3. TCP连接
  4. http请求
  5. 服务器处理请求并返回http报文
  6. 浏览器渲染页面
  7. 断开TCP连接

浏览器内核

内核的种类很多,常见的浏览器内核可以分这四种:Trident、Gecko、Blink、Webkit

IE:Trident内核,也是俗称的IE内核 Chrome:统称为Chromium内核或Chrome内核,以前是Webkit内核,现在是Blink内核 Firefox:Gecko内核,俗称Firefox内核 Safari:Webkit内核 Opera:最初是自己的Presto内核,后来是Webkit,现在是Blink内核 360、猎豹:IE+Chrome双内核 搜狗、遨游、QQ浏览器:Trident(兼容模式)+Webkit(高速模式) 百度、世界之窗:IE内核 2345浏览器:以前是IE内核,现在也是IE+Chrome双内核明。 原文链接:blog.csdn.net/weixin_5951…

常见浏览器兼容问题及解决方案

问题:金额的前缀不显示,即¥这个标识显示不出来

原因: 低版本浏览器对utf-8字符¥不识别

解决方案:用html实体编号替代羊角,代码如下

<sub lang="en">&#165;</sub> 

对箭头函数的理解()=>{}

  1. 箭头函数内的this是静态的,this指向不能改变,只会从它作用域链的上一层继承this;箭头函数的this指向的是父级作用域的this,是通过查找作用域链来确定this的值,也就是看的上下文的this,指向的是定义它的对象而不是使用时所在的对象
  2. this始终指向函数声明时所在作用域的this
  3. 箭头函数不能用作构造函数,不可以使用new命令
  4. 箭头函数不存在arguments对象,不能用伪数组去接收参数,可以用rest参数代替

不适用于与this相关的回调、事件回调、对象方法的回调

适用于与this无关的回调、定时器、数组方法的回调

箭头函数setTimeout严格模式同时存在,其规定权重如下:箭头函数 > setTimeout > 严格模式

关于javascript:this指向的几种情况

数组去重方法

数组去重10种方法

addEventListener中的第三个参 数

是useCapture, 一个bool类型。当为false时为冒泡获取(由里向外),true为capture捕获方式(由外向里)。

Dom事件流

1、概念

DOM事件流相当于将事件捕获与事件冒泡两者结合起来,事件触发的顺序是先进行事件捕获阶段 => 目标元素阶段 => 事件冒泡阶段。

事件捕获:从document到触发事件的那个节点,自上而下的去触发事件。

事件冒泡:从触发事件的那个节点一直到document,是自下而上的去触发事件。

除了focus、blur、scroll其他基本都可以冒泡event.bubble() === true

event.stopPropagation() 阻止事件的捕获和冒泡

event.stopImmediatePropagation() 阻止事件的捕获和冒泡并阻止同组件其他事件绑定执行

event.cancelBubble = true 阻止冒泡(兼容ie8以前)

2、图解

img

图片懒加载

getBoundingRect() api获取图片元素大小和相对于视口的位置

图片元素的offsetTop 判断是否小于 document.body.clientHeight 与 document.body.scrollTop的和

intersection Observer 交叉观察器

XSS 和 CSRF攻击

【前端安全系列之XSS】4分钟彻底了解XSS攻击与防范,面试不再怕

XSS -- 侧重于脚本,注入恶意脚本

url参数注入、输入框注入

image-20221229165204619

image-20221229165502984

CSRF -- 不注入恶意脚本,侧重于请求伪造,在用户不知情情况下用用户身份干坏事

image-20221229165043929

JS原生拖拽实现

  1. 标签添加draggable="true"

    监听相关事件

    拖动元素相关事件

    事件描述
    dragstart用户开始拖动元素时触发
    drag元素正在拖动时触发
    dragend用户完成元素拖动后触发

    容器相关事件如下所示:

    事件描述
    dragenter当被鼠标拖动的对象进入其容器范围内时触发此事件
    dragover当被拖动的对象在另一对象容器范围内拖动时触发此事件
    dragleave当被鼠标拖动的对象离开其容器范围内时触发此事件
    drop在一个拖动过程中,释放鼠标键时触发此事件
  2. 监听鼠标事件改变元素位置

    鼠标按下 mousedown 鼠标移动 mousemove 鼠标松开 mouseup

正则表达式常用方法

正则表达式相关方法介绍

  1. exec() 方法是一个正则表达式方法,用于检索字符串中的正则表达式的匹配。函数返回一个数组中,其中数组中存放匹配的结果;如果未找到匹配,则返回值为null。

  2. test() 方法是一个正则表达式方法,用于检测一个字符串是否匹配某个模式,如果字符串中含有匹配的文本,则返回true,否则返回false。

  3. search() 方法用于检索字符串中指定的子字符串,或检索与正则表达式相配的子字符串,并返回子串的起始位置。

    1. search() 方法使用正则表达式,使用正则表达式搜索字符串,且不区分大小写
    2. search() 方法使用字符串, 可使用字符串作为参数。字符串参数会转换为正则表达式
  4. replace() 方法用于在字符串中用一些字符替换另一些字符,或替换一个与正则表达式匹配的子字符串。

    1. replace() 方法使用正则表达式,使用正则表达式且不区分大小写将方法中的参数替换为字符串中的指定内容。
    2. replace() 方法使用字符串,将接收字符串作为参数。
  5. match() 方法可在字符串内检索指定的值,或找到一个或多个正则表达式的匹配。

JavaScript的原型继承实现方式(另一种为class继承)

  1. 定义新的构造函数,并在内部用call()调用希望“继承”的构造函数,并绑定this
  2. 借助中间函数F实现原型链继承,最好通过封装的inherits函数完成;
  3. 继续在新的构造函数的原型上定义新方法。
function inherits(Child, Parent) {
    var F = function () {};
    F.prototype = Parent.prototype;
    Child.prototype = new F();
    Child.prototype.constructor = Child;
}

SPA(单页应用)首屏加载速度慢怎么解决?

腾讯云原文链接

  • 减小入口文件积
  • 静态资源本地缓存
  • UI框架按需加载
  • 图片资源的压缩
  • 组件重复打包
  • 开启GZip压缩
  • 使用SSR

ES6中类的静态方法=> static 的使用

类就是实例的原型,以前我们一般会new一个test(),有在类中(test)定义的方法,都会被实例继承。如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”

class Father {
    static testMethod() {
        return 'hello';
    }
}

Father.testMethod() // 'hello'
var Child = new Father();
Child.testMethod()
// TypeError: Child.testMethod is not a function

//这是因为Father中的testMethod方法是静态方法(有static关键字),不会被实例化出来的Child继承,
//当前testMethod方发可以直接在Father类上调用,如果在实例化出来的类上调用会抛出一个错误,表示不存在该方法。

// 父类的静态方法可以被子类继承
class Child extends Father {
    Child.testMethod() // 'hello'
}
// 父类Father有一个静态方法,子类Child可以调用这个方法,因为这是通过extends继承的,不是通过new()实例化得到的

ES6的class中的静态属性和方法 static关键字

位置-回到顶部

offset().left 当前元素距离浏览器左侧距离

offset().top 当前元素距离浏览器顶部距离

scrollLeft 距离左侧滚动距离

scrollTop 距离顶部滚动距离

回顶 => animate({scrollTop: 0}, 500)

script标签的defer和async有什么区别

没有defer和async属性时,浏览器立刻加载并执行js脚本

有async(h5后出现)时,加载和渲染后面元素将和script的加载和执行并行(异步),不会阻塞页面渲染,先加载出来先执行

有defer(较早)时,加载和渲染后面元素将和script的加载并行(异步),等所有元素解析完成后才执行。按顺序加载执行

如何优化前端页面的加载速度?有哪些常见的方法可以用来优化前端页面的加载速度?

以下是一些可以用来优化前端页面加载速度的方法:

  1. 懒加载:只加载需要使用的资源,例如图片、视频、音频、脚本和样式等,而不是一次性加载所有资源,以减少页面加载时间。
  2. 使用缓存:使用浏览器缓存或内容分发网络 (CDN) 来缓存常用的资源,以减少重复下载的资源数量。
  3. 压缩和缩小文件大小:压缩和缩小图片、字体和其他资源文件的大小,可以显著减少页面加载时间。
  4. 减少 HTTP 请求:合并 CSS 和 JavaScript 文件,使用图片的 base64 编码,避免使用外部资源等方式可以减少 HTTP 请求次数。
  5. 使用 CDN:使用 CDN 可以加快资源加载速度,因为资源可以从距离用户更近的服务器上下载。
  6. 使用响应式网页设计:响应式网页设计可以让页面根据设备类型自动适配,减少资源加载时间。
  7. 使用浏览器插件:使用浏览器插件可以禁用浏览器默认的行为,例如自动加载图片等,从而减少资源加载时间。

这些方法都可以用来优化前端页面的加载速度。

uniapp和原生微信小程序的区别和优缺点,uniapp遇到的坑

token存在sessionStorage还是localStorage里

token: 验证身份的令牌,一般是用户登录后服务端通过加密等操作返回的字符串

存在localStorage里:每次请求接口都要使用一个字段发送给后端

容易收到xss攻击,做好对应措施利大于弊

存在cookie里:可以自动发送但是缺点是不能跨域

容易受到csrf攻击

你可以用 for of 遍历 Object 吗(迭代器相关)

面试官:你可以用 for of 遍历 Object 吗?

迭代器模式:迭代器模式提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露该对象的内部表示。

遍历数组

  1. for 循环
  2. forEach
  3. map
  4. reduce
  5. keys
  6. values
  7. for of
  8. ...

其中keys values for of 需要Iterator支持

遍历 Map/Set

  1. keys
  2. entries
  3. forEach
  4. ......

遍历 Object

  1. for in
  2. Object.keys(obj)得到对象每个属性的数组, 然后使用数组的遍历方法遍历每个 key,就能获取 每个 key 对应的 value

Iteratorfor of

Iterator是ES6提出的一个接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作。

Iterator 的作用

  1. 为各种数据结构,提供一个统一的、简便的访问接口。
  2. ES6提出了新的遍历命令for...of循环,Iterator 接口主要供for...of消费

Iterator遍历过程

  1. Iterator 接口返回了一个有next方法的对象。
  2. 每调用一次 next,依次返回了数组中的项,直到它指向数据结构的结束位置。
  3. 返回的结果是一个对象,对象中包含了当前值value 和 当前是否结束done

for of 直接遍历object会报错(obj is not Iterable), 我们需要给对象部署Iterator接口(其实就是在Object.prototype上实现一个以Symbol.iterator为名的function,这个function返回一个有next方法的对象,每调用一次 next, 能够依次返回数组中的项,直到它指向数据结构的结束位置 )

function objectIterator() {
  const keys = Object.keys(this)
  let index = 0
  return {
    next: () => {
      const done = index >= keys.length
      const value = done ? undefined : this[keys[index]]
      index++
      return {
        done,
        value
      }
    }
  }
}

Object.prototype[Symbol.iterator] = objectIterator

const obj = {
  key: '1',
  value: '2'
}

for (const iterator of obj) {
  console.log(iterator)
}

纯函数

不依赖于除参数外的其他外部作用域变量,也不修改其作用域外其他变量的函数(如数组Array.slice)

  • 确定性:相同入参一定会有相同输出
  • 无副作用:除返回函数值外不产生附加影响(例如:发起http请求、修改全局变量、修改参数或改变外部存储)

作用

降低bug发生的概率、便于进行单元测试、增强可读性可维护性