热搜:前端 nest neovim nvim

2022年前端面试题整理,持续更新中

lxf2023-06-12 02:10:24

端面试题整理

已同步到编程、CSDN

编程地址: /post/707533…

CSDN 地址:blog.csdn.net/z1832729975…

个人整理了很多网上常见的面试题,希望也能通过这来复习 内容有点多,可能 CSDN 上预览效果不好,想要 markdown 文档的可以私信我,推荐使用Typora

2022年前端面试题整理,持续更新中

比较好的面试题

2021 年我的前端面试准备 2021 年前端面试必读文章【超三百篇文章/赠复习导图】

CSS、HTML

你是怎么理解 HTML 语义化

HTML 语义化简单来说就是用正确的标签来做正确的事。 比如表示段落用 p 标签、表示标题用 h1-h6 标签、表示文章就用 article 等。

DOCTYPE 的作用

Doctype 作用?严格模式与混杂模式如何区分?它们有何差异?

  1. <!DOCTYPE> 声明位于文档中的最前面,处于 <html> 标签之前。告知浏览器以何种模式来渲染文档。

  2. 严格模式的排版和 JS 运作模式是 以该浏览器支持的最高标准运行。

  3. 在混杂模式中,页面以宽松的向后兼容的方式显示。模拟老式浏览器的行为以防止站 点无法工作。

  4. DOCTYPE 不存在或格式不正确会导致文档以混杂模式呈现。 你知道多少种 Doctype 文档类型? 该标签可声明三种 DTD 类型,分别表示严格版本、过渡版本以及基于框架的 HTML 文档。 HTML 4.01 规定了三种文档类型:Strict、Transitional 以及 Frameset。 XHTML 1.0 规定了三种 XML 文档类型:Strict、Transitional 以及 Frameset。 Standards (标准)模式(也就是严格呈现模式)用于呈现遵循最新标准的网页,而 Quirks (包容)模式(也就是松散呈现模式或者兼容模式)用于呈现为传统浏览器而设计的网页。

行内元素、块级元素、 空元素有那些?

  • 行内元素 (不能设置宽高,设置宽高无效) a,span,i,em,strong,label
  • 行内块元素:img, input
  • 块元素: div, p, h1-h6, ul,li,ol,dl,table...
  • 知名的空元素 br, hr,img, input,link,meta

可以通过 display 修改 inline-block, block, inline

注意

只有文字才能组成段落,因此 p 标签里面不能放块级元素,特别是 p 标签不能放 div。同理还有这些标签h1,h2,h3,h4,h5,h6,dt ,他们都是文字类块级标签,里面不能放其他块级元素。

html5 新特性

HTML5 新特性

  1. 语义化标签, header, footer, nav, aside,article,section
  2. 增强型表单
  3. 视频 video 和音频 audio
  4. Canvas 绘图
  5. SVG绘图
  6. 地理定位
  7. 拖放 API
  8. WebWorker
  9. WebStorage( 本地离线存储 localStorage、sessionStorage )
  10. WebSocket

css3 新特性

CSS3 有哪些新特性?CSS3 新特性详解

1、圆角效果;2、图形化边界;3、块阴影与文字阴影;4、使用 RGBA 实现透明效果;5、渐变效果;6、使用“@Font-Face”实现定制字体;7、多背景图;8、文字或图像的变形处理;9、多栏布局;10、媒体查询等。

1、颜色:新增RGBA、HSLA模式
2、文字阴影:(text-shadow3、边框:圆角(border-radius)边框阴影:box-shadow
4、盒子模型:box-sizing
5、背景:background-size,background-origin background-clip(削弱)
6、渐变:linear-gradient(线性渐变):
eg: background-image: linear-gradient(100deg, #237b9f, #f2febd);
radial-gradient (径向渐变)
7、过渡:transition可实现动画
8、自定义动画: animate@keyfrom
9、媒体查询:多栏布局@media screen and (width:800px)
10、border-image
112D转换:transform:translate(x,y) rotate(x,y)旋转 skew(x,y)倾斜 scale(x,y)缩放
123D转换
13、字体图标:Font-Face
14、弹性布局:flex

css 选择器

id 选择器( #myid)

类选择器(.myclassname)

标签选择器(div, h1, p)相邻选择器(h1 + p)

子选择器(ul > li)后代选择器(li a)

属性选择器(a[rel = "external"])

伪类选择器(a: hover, li:nth-child)

通配符选择器( * )

        !Important > 行内式 > id > 类/伪类/属性 > 标签选择器  >  全局
    (对应权重:无穷大∞ > 1000> 100 >  10        >  1   >      0

盒模型

一个盒子,会用 content,padding,border,margin 四部分,

标准的盒模型的宽高指的是 content 部分

ie 的盒模型的宽高包括了 content+padding+border

我们可以通过 box-sizing 修改盒模型,box-sizing border-box content-box

margin 合并

在垂直方向上的两个盒子,他们的 margin 会发生合并(会取最大的值),比如上边盒子设置margin-bottom:20px,下边盒子设置margin-top:30px;,那么两个盒子间的间距只有30px,不会是50px

解决 margin 合并,我们可以给其中一个盒子套上一个父盒子,给父盒子设置 BFC

margin 塌陷

效果: 一个父盒子中有一个子盒子,我们给子盒子设置margin-top:xxpx结果发现会带着父盒子一起移动(就效果和父盒子设置margin-top:xxpx的效果一样)

解决: 1、给父盒子设置 border,例如设置border:1px solid red; 2、给父盒子设置 BFC

BFC

块级格式化上下文 (block format context)

BFC 的布局规则 *

  • 内部的 Box 会在垂直方向,一个接一个地放置。
  • Box 垂直方向的距离由 margin 决定。属于同一个 BFC 的两个相邻 Box 的 margin 会发生重叠。
  • 每个盒子(块盒与行盒)的 margin box 的左边,与包含块 border box 的左边相接触(对于从左往右的格式化,否则相反)。即使存在浮动也是如此。
  • BFC 的区域不会与 float box 重叠。
  • BFC 就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素。反之也如此。
  • 计算 BFC 的高度时,浮动元素也参与计算。

触发 BFC 的条件 *

  • 根元素 html
  • float 的值不是 none。
  • position 的值 absoute、fixed
  • display 的值是 inline-block、table-cell、flex、table-caption 或者 inline-flex
  • overflow 的值不是 visible

解决什么问题

  1. 可以用来解决两栏布局BFC 的区域不会与 float box 重叠

    .left {
      float: flet;
    }
    .right {
      overflow: hidden;
    }
    
  2. 解决 margin 塌陷和 margin 合并问题

  3. 解决浮动元素无法撑起父元素

flex

设为 Flex 布局以后,子元素的 float、clear 和 vertical-align 属性将失效

什么是 rem、px、em 区别

rem 是一个相对单位,rem 的是相对于 html 元素的字体大小没有继承性

em 是一个相对单位,是相对于父元素字体大小有继承性

px 是一个“绝对单位”,就是 css 中定义的像素,利用 px 设置字体大小及元素的宽高等,比较稳定和精确。

响应式布局

响应式布局有哪些实现方式?什么是响应式设计?响应式设计的基本原理是什么?

1.百分比布局,但是无法对字体,边框等比例缩放

2.弹性盒子布局 display:flex

3.rem 布局,1rem=html 的 font-size 值的大小

  1. css3 媒体查询 @media screen and(max-width: 750px){}

5.vw+vh

6.使用一些框架(bootstrap,vant)

什么是响应式设计:响应式网站设计是一个网站能够兼容多个终端,智能地根据不同设备环境进行相对应的布局

响应式设计的基本原理:基本原理是通过媒体查询检测不同的设备屏幕尺寸设置不同的 css 样式 页面头部必须有 meta 声明的

布局

  • 两栏布局,左边定宽,右边自适应
  • 三栏布局、圣杯布局、双飞翼布局

水平垂直居中

方法一:给父元素设置成弹性盒子,子元素横向居中,纵向居中

方法二:父相子绝后,子部分向上移动本身宽度和高度的一半,也可以用 transfrom:translate(-50%,-50%)(最常用方法)

方法三:父相子绝,子元素所有定位为 0,margin 设置 auto 自适应

DOM 事件机制/模型

DOM0 级模型、IE 事件模型、DOM2 级事件模型

就比如用户触发一个点击事件,有一个触发的过程

事件捕获-阶段(从上大小,从外到内)--->处于目标事件-阶段---->事件冒泡-阶段(从下到上,从内到外)

window.addEventListener(
  "click",
  function (event) {
    event = event || window.event /*ie*/;

    const target = event.target || event.srcElement; /*ie*/ // 拿到事件目标

    // 阻止冒泡
    // event.stopPropagation()
    // event.cancelBubble=true; // ie

    // 阻止默认事件
    // event.preventDefault();
    // event.returnValue=false; // ie
  },
  /* 是否使用捕获,默认是fasle, */ fasle
);

2022年前端面试题整理,持续更新中

事件委托

简介:事件委托指的是,不在事件的发生地(直接 dom)上设置监听函数,而是

在其父元素上设置监听函数,通过事件冒泡,父元素可以监听到子元素上事件的

触发,通过判断事件发生元素 DOM 的类型,来做出不同的响应。

举例:最经典的就是 ul 和 li 标签的事件监听,比如我们在添加事件时候,采用

事件委托机制,不会在 li 标签上直接添加,而是在 ul 父元素上添加。

好处:比较合适动态元素的绑定,新添加的子元素也会有监听函数,也可以有事

件触发机制

JavaScript

js 数据类型

8 中, ES6出的 Symbol BigInt

Number String Boolean undefined null Object Symbol BigInt

js 的基本数据类型和复杂数据类型的区别(在堆和栈中,赋值时的不同,一个拷贝值一个拷贝地址)

基本类型和引用类型在内存上存储的区别

2022年前端面试题整理,持续更新中

null 与 undefined 的异同

相同点:

  • Undefined 和 Null 都是基本数据类型,这两个基本数据类型分别都只有一个值,就是 undefined 和 null

不同点:

  • null 转换成数字是 0, undefined 转换数字是NaN

  • undefined 代表的含义是未定义, null 代表的含义是空对象。

  • typeof null 返回'object',typeof undefined 返回'undefined'

  • null == undefined; // true
    null === undefined; // false
    ;
    

- 其实 null 不是对象,虽然 typeof null 会输出 object,但是这只是 JS 存在的一个悠久 Bug。在 JS 的最初版本中使用的是 32 位系统,为了性能考虑使用低位存储变量的类型信息,000 开头代表是对象,然而 null 表示为全零,所以将它错误的判断为 object 。虽然现在的内部类型判断代码已经改变了,但是对于这个 Bug 却是一直流传下来。

**说说 JavaScript 中判断数据类型的几种方法**

**typeof**

- `typeof`一般用来判断基本数据类型,**除了判断 null 会输出"object",其它都是正确的**
- `typeof`判断引用数据类型时,**除了判断函数会输出"function",其它都是输出"object"**

**instanceof**

> Instanceof 可以准确的判断引用数据类型,它的原理是检测构造函数的`prototype`属性是否在某个实例对象的原型链上, 不能判断基本数据类型

​```js
// instanceof 的实现
function instanceofOper(left, right) {
  const prototype = right.prototype;
  while (left) {
    if ((left = left.__proto__) === prototype) {
      return true;
    }
  }
  return false;
}
// let obj  = {}
// Object.getPrototypeOf(obj) === obj.__proto__ ==> true

// 实现 instanceof 2
function myInstanceof(left, right) {
  // 这里先用typeof来判断基础数据类型,如果是,直接返回false
  if (typeof left !== "object" || left === null) return false;
  // getProtypeOf是Object对象自带的API,能够拿到参数的原型对象
  let proto = Object.getPrototypeOf(left);
  while (true) {
    if (proto === null) return false;
    if (proto === right.prototype) return true; //找到相同原型对象,返回true
    proto = Object.getPrototypeof(proto);
  }
}

Object.prototype.toString.call() 返回 [object Xxxx] 都能判断

深拷贝和浅拷贝

let obj = { b: "xxx" };
let arr = [{ a: "ss" }, obj, 333];

// 赋值
let arr2 = arr;
// 浅拷贝-只拷贝了一层,深层次的引用还是存在
// Object.assign(), ...扩展运算符,slice等
let arr2 = arr.slice();
let arr2 = [...arr];
obj.b = "222"; // arr2[1].b => 222
// arr[2] = 4444 ==> arr2[2] ===> 333

// 深拷贝
// 最简单的
let arr2 = JSON.parse(JSON.stringify(arr));
// undefined 丢失,如果里面有 函数,还有循环引用就会报错
// 自己封装,要递归处理

实现深拷贝-简单版

export function deepClone(obj, map = new Map()) {
  if (!obj && typeof obj !== "object") return obj;
  if (obj instanceof Date) return new Date(obj);
  if (obj instanceof RegExp) return new RegExp(obj.source, obj.flags);

  if (map.get(obj)) {
    //  如果有循环引用、就返回这个对象
    return map.get(obj);
  }

  const cloneObj = obj.constructor(); // 数组的就是[],对象就是{}

  map.set(obj, cloneObj); // 缓存对象,用于循环引用的情况

  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      cloneObj[key] = deepClone(obj[key], map);
    }
  }

  return cloneObj;
}

模块化

  • commonjs

    // 由nodejs实现
    const fs = require("fs");
    module.exports = {};
    
  • ESM

    // 由es6实现
    import $ from "jquery";
    export default $;
    
  • AMD(异步加载模块)

    // 由RequireJS实现
    define(["juqery", "vue"], function ($, Vue) {
      // 依赖必须一开始就写好
      $("#app");
      new Vue({});
    });
    
  • CMD

    // 由SeaJS 实现
    define(function(require, exports, module) {
        var a = require('./a')
        a.doSomething()
        // ....
        var b = require('./b') // 依赖可以就近书写
        b.doSomething()
       // ...
    })
    
  • UMD (通用加载模块)

    (function (global, factory) {
      typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
      typeof define === 'function' && define.amd ? define(factory) :
      (global = global || self, global.Vue = factory());
    }(this, function () { 'use strict';
    

AMD 和 CMD 的区别有哪些

blog.csdn.net/qq_38912819…

  1. 对于依赖的模块,AMD 是提前执行,CMD 是延迟执行。不过 RequireJS 从 2.0 开始,也改成可以延迟执行(根据写法不同,处理方式不同)
  2. CMD 推崇依赖就近,AMD 推崇依赖前置

CommonJS 与 ES6 Module 的差异

CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。

  • CommonJS 模块输出的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。
  • ES6 模块的运行机制与 CommonJS 不一样。JS 引擎对脚本静态分析的时候,遇到模块加载命令import,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。换句话说,ES6 的import有点像 Unix 系统的“符号连接”,原始值变了,import加载的值也会跟着变。因此,ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。

CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。

  • 运行时加载: CommonJS 模块就是对象;即在输入时是先加载整个模块,生成一个对象,然后再从这个对象上面读取方法,这种加载称为“运行时加载”。
  • 编译时加载: ES6 模块不是对象,而是通过 export 命令显式指定输出的代码,import时采用静态命令的形式。即在import时可以指定加载某个输出值,而不是加载整个模块,这种加载称为“编译时加载”。

CommonJS 加载的是一个对象(即 module.exports 属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。

JS 延迟加载的方式

JavaScript 会阻塞 DOM 的解析,因此也就会阻塞 DOM 的加载。所以有时候我们希望延迟 JS 的加载来提高页面的加载速度。

  • 把 JS 放在页面的最底部
  • script 标签的 defer 属性:脚本会立即下载但延迟到整个页面加载完毕再执行。该属性对于内联脚本无作用 (即没有 「src」 属性的脚本)。
  • Async 是在外部 JS 加载完成后,浏览器空闲时,Load 事件触发前执行,标记为 async 的脚本并不保证按照指定他们的先后顺序执行,该属性对于内联脚本无作用 (即没有 「src」 属性的脚本)。
  • 动态创建 script 标签,监听 dom 加载完毕再引入 js 文件

call、apply 、bind

call,apply, bind 都是改变 this 指向,bind 不会立即执行,会返回的是一个绑定 this 的新函数

面试官问:能否模拟实现 JS 的 call 和 apply 方法

obj.call(this指向, 参数1, 参数2)ss
obj.apply(this指向, [参数1, 参数2])

function fn(age) {
    console.log(this, age)
}
const obj = {name:''}
const result = fn.bind(obj) // bind会返回一个新的函数
result(20)
// 实现一个 apply
Function.prototype.myApply = function (context) {
  context = context || window;
  const fn = Symbol();
  context[fn] = this;
  var res = context[fn](...arguments[1]);
  delete context[fn];
  return res;
};

实现一个 bind

// 最终版 删除注释 详细注释版请看上文
Function.prototype.bind =
  Function.prototype.bind ||
  function bind(thisArg) {
    if (typeof this !== "function") {
      throw new TypeError(this + " must be a function");
    }
    var self = this;
    var args = [].slice.call(arguments, 1);
    var bound = function () {
      var boundArgs = [].slice.call(arguments);
      var finalArgs = args.concat(boundArgs);
      if (this instanceof bound) {
        if (self.prototype) {
          function Empty() {}
          Empty.prototype = self.prototype;
          bound.prototype = new Empty();
        }
        var result = self.apply(this, finalArgs);
        var isObject = typeof result === "object" && result !== null;
        var isFunction = typeof result === "function";
        if (isObject || isFunction) {
          return result;
        }
        return this;
      } else {
        return self.apply(thisArg, finalArgs);
      }
    };
    return bound;
  };

防抖

debounce 所谓防抖,就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。

function debounce(func, wait, immediate) {
  let timeout;
  return function () {
    const context = this;
    const args = [...arguments];
    if (timeout) clearTimeout(timeout);
    if (immediate) {
      const callNow = !timeout;
      timeout = setTimeout(() => {
        timeout = null;
      }, wait);
      if (callNow) func.apply(context, args);
    } else {
      timeout = setTimeout(() => {
        func.apply(context, args);
      }, wait);
    }
  };
}

节流

就是指连续触发事件但是在 n 秒中只执行一次函数

function throttle(fn, wait) {
  let pre = 0;
  return function (...args) {
    let now = Date.now();
    if (now - pre >= wait) {
      fn.apply(this, args);
      pre = now;
    }
  };
}

闭包

闭包是指有权访问另一个函数作用域中的变量的函数 ——《JavaScript 高级程序设计》

当函数可以记住并访问所在的词法作用域时,就产生了闭包,

即使函数是在当前词法作用域之外执行 ——《你不知道的 JavaScript》

  • 闭包用途:
    1. 能够访问函数定义时所在的词法作用域(阻止其被回收)
    2. 私有化变量
    3. 模拟块级作用域
    4. 创建模块
  • 闭包缺点:会导致函数的变量一直保存在内存中,过多的闭包可能会导致内存泄漏

原型、原型链(高频)

原型: 对象中固有的__proto__属性,该属性指向对象的prototype原型属性。

原型链: 当我们访问一个对象的属性时,如果这个对象内部不存在这个属性,那么它就会去它的原型对象里找这个属性,这个原型对象又会有自己的原型,于是就这样一直找下去,也就是原型链的概念。原型链的尽头一般来说都是Object.prototype所以这就是我们新建的对象为什么能够使用toString()等方法的原因。

特点: JavaScript对象是通过引用来传递的,我们创建的每个新对象实体中并没有一份属于自己的原型副本。当我们修改原型时,与之相关的对象也会继承这一改变。

this 指向、new 关键字

this对象是是执行上下文中的一个属性,它指向最后一次调用这个方法的对象,在全局函数中,this等于window,而当函数被作为某个对象调用时,this 等于那个对象。 在实际开发中,this 的指向可以通过四种调用模式来判断。

  1. 函数调用,当一个函数不是一个对象的属性时,直接作为函数来调用时,this指向全局对象。
  2. 方法调用,如果一个函数作为一个对象的方法来调用时,this指向这个对象。
  3. 构造函数调用,this指向这个用new新创建的对象。
  4. 第四种是 apply 、 call 和 bind 调用模式,这三个方法都可以显示的指定调用函数的 this 指向。apply接收参数的是数组,call接受参数列表,`` bind方法通过传入一个对象,返回一个this绑定了传入对象的新函数。这个函数的 this指向除了使用new `时会被改变,其他情况下都不会改变。

new

面试官问:能否模拟实现 JS 的 new 操作符

  1. 首先创建了一个新的空对象
  2. 设置原型,将对象的原型设置为函数的prototype对象。
  3. 让函数的this指向这个对象,执行构造函数的代码(为这个新对象添加属性)
  4. 判断函数的返回值类型,如果是值类型,返回创建的对象。如果是引用类型,就返回这个引用类型的对象。
// new 操作符的实现
function newOperator(ctor) {
  if (typeof ctor !== "function") {
    throw "newOperator function the first param must be a function";
  }
  newOperator.target = ctor;
  var newObj = Object.create(ctor.prototype);
  var argsArr = [].slice.call(arguments, 1);
  var ctorReturnResult = ctor.apply(newObj, argsArr);
  var isObject =
    typeof ctorReturnResult === "object" && ctorReturnResult !== null;
  var isFunction = typeof ctorReturnResult === "function";
  if (isObject || isFunction) {
    return ctorReturnResult;
  }
  return newObj;
}

作用域、作用域链、变量提升

作用域负责收集和维护由所有声明的标识符(变量)组成的一系列查询,并实施一套非常严格的规则,确定当前执行的代码对这些标识符的访问权限。(全局作用域、函数作用域、块级作用域)。 作用域链就是从当前作用域开始一层一层向上寻找某个变量,直到找到全局作用域还是没找到,就宣布放弃。这种一层一层的关系,就是作用域链

继承(含 es6)、多种继承方式

function Animal(name) {
  // 属性
  this.name = name || "Animal";
  // 实例方法
  this.sleep = function () {
    console.log(this.name + "正在睡觉!");
  };
}
// 原型方法
Animal.prototype.eat = function (food) {
  console.log(this.name + "正在吃:" + food);
};

(1)第一种是以原型链的方式来实现继承,但是这种实现方式存在的缺点是,在包含有引用类型的数据时,会被所有的实例对象所共享,容易造成修改的混乱。还有就是在创建子类型的时候不能向超类型传递参数。

// 原型链继承
function Cat() {}
Cat.prototype = new Animal("小黄"); // 缺点 无法实现多继承 来自原型对象的所有属性被所有实例共享
Cat.prototype.name = "cat";

(2)第二种方式是使用借用构造函数的方式,这种方式是通过在子类型的函数中调用超类型的构造函数来实现的,这一种方法解决了不能向超类型传递参数的缺点,但是它存在的一个问题就是无法实现函数方法的复用,并且超类型原型定义的方法子类型也没有办法访问到。

// 借用构造函数继承
function Cat() {
  Animal.call(this, "小黄");
  // 缺点 只能继承父类实例的属性和方法,不能继承原型上的属性和方法。
}

(3)第三种方式是组合继承,组合继承是将原型链和借用构造函数组合起来使用的一种方式。通过借用构造函数的方式来实现类型的属性的继承,通过将子类型的原型设置为超类型的实例来实现方法的继承。这种方式解决了上面的两种模式单独使用时的问题,但是由于我们是以超类型的实例来作为子类型的原型,所以调用了两次超类的构造函数,造成了子类型的原型中多了很多不必要的属性。

(4)第四种方式是原型式继承,原型式继承的主要思路就是基于已有的对象来创建新的对象,实现的原理是,向函数中传入一个对象,然后返回一个以这个对象为原型的对象。这种继承的思路主要不是为了实现创造一种新的类型,只是对某个对象实现一种简单继承,ES5 中定义的 Object.create() 方法就是原型式继承的实现。缺点与原型链方式相同。

function object(o) {
  function F() {}
  F.prototype = o;
  return new F();
}

(5)第五种方式是寄生式继承,寄生式继承的思路是创建一个用于封装继承过程的函数,通过传入一个对象,然后复制一个对象的副本,然后对象进行扩展,最后返回这个对象。这个扩展的过程就可以理解是一种继承。这种继承的优点就是对一个简单对象实现继承,如果这个对象不是我们的自定义类型时。缺点是没有办法实现函数的复用。

function createAnother(original) {
  var clone = object(original); //通过调用object函数创建一个新对象
  clone.sayHi = function () {
    //以某种方式来增强这个对象
    alert("hi");
  };
  return clone; //返回这个对象
}

(6)第六种方式是寄生式组合继承,组合继承的缺点就是使用超类型的实例做为子类型的原型,导致添加了不必要的原型属性。寄生式组合继承的方式是使用超类型的原型的副本来作为子类型的原型,这样就避免了创建不必要的属性。

function extend(subClass, superClass) {
  var prototype = object(superClass.prototype); //创建对象
  prototype.constructor = subClass; //增强对象
  subClass.prototype = prototype; //指定对象
}

类型转换

大家都知道 JS 中在使用运算符号或者对比符时,会自带隐式转换,规则如下:

-、*、/、% :一律转换成数值后计算

+:

  • 数字 + 字符串 = 字符串, 运算顺序是从左到右

  • 数字 + 对象, 优先调用对象的 valueOf -> toString

  • 数字 + boolean/null -> 数字

  • 数字 + undefined -> NaN

  • [1].toString() === '1' 内部调用 .join 方法

  • {}.toString() === '[object object]'

  • NaN !== NaN 、+undefined 为 NaN

Object.is()与比较操作符=====的区别?

  • ==会先进行类型转换再比较
  • ===比较时不会进行类型转换,类型不同则直接返回 false
  • Object.is()===基础上特别处理了NaN,-0,+0,保证-0 与+0 不相等,但 NaN 与 NaN 相等

==操作符的强制类型转换规则

  • 字符串和数字之间的相等比较,将字符串转换为数字之后再进行比较。
  • 其他类型和布尔类型之间的相等比较,先将布尔值转换为数字后,再应用其他规则进行比较。
  • null 和 undefined 之间的相等比较,结果为真。其他值和它们进行比较都返回假值。
  • 对象和非对象之间的相等比较,对象先调用 ToPrimitive 抽象操作后,再进行比较。
  • 如果一个操作值为 NaN ,则相等比较返回 false( NaN 本身也不等于 NaN )。
  • 如果两个操作值都是对象,则比较它们是不是指向同一个对象。如果两个操作数都指向同一个对象,则相等操作符返回 true,否则,返回 false。

2022年前端面试题整理,持续更新中

ES6

  1. 新增 Symbol 类型 表示独一无二的值,用来定义独一无二的对象属性名;
  2. const/let 都是用来声明变量,不可重复声明,具有块级作用域。存在暂时性死区,不存在变量提升。(const 一般用于声明常量);
  3. 变量的解构赋值(包含数组、对象、字符串、数字及布尔值,函数参数),剩余运算符(...rest);
  4. 模板字符串(${data});
  5. ...扩展运算符(数组、对象);;
  6. 箭头函数;
  7. Set 和 Map 数据结构;
  8. Proxy/Reflect;
  9. Promise;
  10. async 函数;
  11. Class;
  12. Module 语法(import/export)。

let/const

const声明一个只读的常量。一旦声明,常量的值就不能改变 es6.ruanyifeng.com/#docs/let

var 在全局作用域中声明的变量会变成全局变量

let、const 和 var 的区别

  • 不允许重复声明

  • 不存在变量提升

    // var 的情况
    console.log(foo); // 输出undefined
    var foo = 2;
    
    // let 的情况
    console.log(bar); // 报错ReferenceError
    let bar = 2;
    
  • 暂时性死区(不能在未声明之前使用)

    注意暂时性死区和不存在变量提升不是同一个东西

    只要块级作用域内存在let命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响。

    var tmp = 123; // 声明了 tmp
    
    if (true) {
      tmp = "abc"; // ReferenceError
      let tmp;
    }
    
  • 块级作用域:用 let 和 const 声明的变量,在这个块中会形成块级作用域

    es5 只有函数作用域和全局作用域

    IIFE 立即执行函数表达式

    // IIFE 写法
    (function () {
      var tmp = ...;
      ...
    }());
    
    // 块级作用域写法
    {
      let tmp = ...;
      ...
    }
    
    // 函数声明
    function a() {}
    // 函数表达式
    const b = function () {};
    

ES6 的一些叫法

  • reset 参数

    function add(...values) {
      let sum = 0;
    
      for (var val of values) {
        sum += val;
      }
    
      return sum;
    }
    
    add(2, 5, 3); // 10
    
  • 扩展运算符

    console.log(...[1, 2, 3]);
    // 1 2 3
    
    const b = { ...{ a: "2", b: "3" } };
    
  • ?. 可选链运算符

    左侧的对象是否为nullundefined。如果是的,就不再往下运算,而是返回undefined

    a?.b;
    // 等同于
    a == null ? undefined : a.b;
    // 注意 undefined == null ==> true
    
  • ?? Null 判断运算符

es6.ruanyifeng.com/#docs/opera…

const headerText = response.settings.headerText ?? "Hello, world!";
const animationDuration = response.settings.animationDuration ?? 300;
const showSplashScreen = response.settings.showSplashScreen ?? true;

但左侧的为 undefined或者null是就返回右边的,否则就直接返回左边的

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

  1. 箭头函数没有thisthis是继承于当前的上下文,不能通过call,apply,bind去改变 this
  2. 箭头函数没有自己的 arguments 对象,但是可以访问外围函数的 arguments 对象
  3. 不能通过 new 关键字调用(不能作为构造函数),同样也没有 new.target 和原型

vue

vue2 是通过Object.defineProperty来实现响应式的,所以就会有一些缺陷

  1. 当修改一个对象的某个键值属性时,当这个键值没有在这个对象中,vue 不能做响应式处理
  2. 但直接修改数组的某一项(arr[index]='xxx')vue 不能做响应式处理

可用下面的解决响应式

  1. Vue.set ==> this.$set(对象\数组, key 值、index, value)
  2. 修改数组length, 调用数据的 splice 方法

vue 生命周期

beforeCreate 实例化之前这里能拿到this,但是还不能拿到data里面的数据
created  实例化之后
beforeMount()
mounted() $el
beforeUpdate
updated

beforeDestroy 清除定时/移除监听事件
destroyed

// 被keep-alive 包裹的
// keep-alive 标签 include exclude max
activated() {},
deactivated() {},

// 父子
父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted。

// 离开页面:实例销毁 --> DOM卸载
parent  beforeDestroy
child   beforeDestroy
child   destroyed
parent  destroyed

Vue 的 data 为什么是一个函数

因为 Vue 的组件可能会在很多地方使用, 会产生多个实例,如果返回的是对象的, 这些组件之间的数据是同一份(引用关系),那么修改其中一个组件的数据,另外一个组件的数据都会被修改到

Vue key 值的作用

看这个视频,你能给面试官说这些,你就很不错了,vue 和 react 的差不多 www.bilibili.com/video/BV1wy…

...待更新

Vue 双向数据绑定原理

下面是照抄的一段话,个人觉得这个主要看个人理解,如果看过源码理解 MVVM 这方面的,就很简单

vue.js 是采用数据劫持结合发布者-订阅者模式的方式,通过 Object.defineProperty()来劫持

各个属性的 setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。

具体步骤:

第一步:需要 observe 的数据对象进行递归遍历,包括子属性对象的属性,都加上 setter

和 getter

这样的话,给这个对象的某个值赋值,就会触发 setter,那么就能监听到了数据变化

第二步:compile 解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,

并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通

知,更新视图

第三步:Watcher 订阅者是 Observer 和 Compile 之间通信的桥梁,主要做的事情是:

1、在自身实例化时往属性订阅器(dep)里面添加自己

2、自身必须有一个 update()方法

3、待属性变动 dep.notice()通知时,能调用自身的 update()方法,并触发 Compile 中绑定的

回调,则功成身退。

第四步:MVVM 作为数据绑定的入口,整合 Observer、

Compile 和 Watcher 三者,通过 Observer

来监听自己的 model 数据变化,通过 Compile 来解析编译模板指令,最终利用 Watcher 搭起

Observer 和 Compile 之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数

据 model 变更的双向绑定效果。

所以也可以根据这个来说明为什么 给Vue对象不存在的属性设置值的时候不生效,直接修改数组的index不生效

Vue 提供了 Vue.set(对象|数组, key|index, 值)修改触发响应式,重新数组的原型方法实现响应式

Vue extend 和 mixins

vue extend 和 mixins 的区别, mixins 里面的 函数和本身的函数重名了使用哪一个,mixins 里面的生命周期和本身的生命周期哪一个先执行

...待更新

动态组件

// component 动态组件,通过is设置要显示的组件
<component is="UserInfo" >

递归组件

就是给组件设置name,之后就可以在当前组件去递归使用组件

Vue 组件间的传值的几种方式

// Vue组件间的传值的几种方式
1. props/emit
2. $attrs/$listeners // $attrs 除了父级作用域 props、class、style 之外的属性
// $listeners 父组件里面的所有的监听方法
3. $refs/$parent/$children/$root/
4. vuex
5. 事件总线,通过new Vue去实现 / mitt <==> vue3
6. provide/inject
    // 父组件
    props: {},
    provide() {
        name: this.name,
        user: this.user
    }
    // 子组件
    props: {},
    inject: ['user']
7. 本地存储、全局变量

watch、mixins、组件顺序、组件配置

export default {
  name: "MyComponentName",
  mixins: [tableMixin],
  components: {},
  inject: ["xxx"],
  // props: ['value', 'visible'],
  props: {
    id: String,
    type: {
      // required: true,
      type: String,
      default: "warning",
      validator(val) {
        return ["primary", "warning", "danger", "success", "info"].includes(
          val
        );
      },
    },
    list: {
      type: Array,
      default: () => [],
    },
  },
  data() {
    return {
      name: "张三",
      user: { name: "张三", age: 18 },
      loading: true,

      // vue2
      obj: {
        name: "李四~",
      },
      // vue2 会进行深度合并
      // obj  {"name":"李四~","age":19}

      // vue3 { name: "李四~" }
    };
  },
  // provide 不支持响应式,想支持响应式的话我们要传对象
  provide() {
    return {
      userName: this.name,
      user: this.user,
    };
  },
  computed: {
    // fullName() {
    //   return 'xxxxx'
    // }
    fullName: {
      get() {
        return this.$store.state.userName;
        // return '李四'
      },
      set(val) {
        this.$store.commit("SET_NAME", val);
      },
    },
  },

  watch: {
    // name(value) {
    //   this.handlerName()
    // }
    // name: {
    //   immediate: true,
    //   deep: true, //
    //   handler(val, oldValue) {
    //     this.handlerName()
    //   },
    // },
    // this.obj.name = 'xxxx' 这样不会执行
    // this.obj = {name: 'xxx'} 这样才会执行
    // obj(value) {
    //   console.log(' value: ', value)
    // }
    //  和上面等价
    // obj: {
    //   handler(value) {
    //     console.log(" value: ", value)
    //   },
    // },
    // this.obj.name = 'xxxx' 这样去修改也能监听
    // obj: {
    //   deep: true, // 深度监听
    //   immediate: true, // 第一次就用执行这个方法,可以理解为在 created 的时候会执行 handler
    //   handler(value) {
    //     console.log(" value: ", value)
    //   },
    // },
    //
    // obj: {
    //   deep: true, // 深度监听
    //   immediate: true, // 第一次就用执行这个方法,可以理解为在 created 的时候会执行 handler
    //   handler: 'handlerName',
    // },
    // ==》
    // obj: 'handlerName'
    // '$route.path': {},
    // 'obj.a' : {}
  },

  beforeCreate() {
    console.log("this", this);
  },
  mounted() {
    // this.handlerName()
    this.fullName = "xxxx";

    //  this.fullName '李四'
  },

  methods: {
    handlerName() {
      this.obj.name = "xxxx";
    },
  },
};

指令

常用指令

  • v-show dispaly none 的切换

  • v-if/v-else

  • v-html

  • v-text

  • v-for (vue2 v-forv-if优先级高,vu3v-if优先级比v-for高 )

  • v-cloak [v-cloak] {dispaly:none}

  • v-once 静态内容

  • v-bind => : v-on => @

    <!--- 可以直接 v-bind="object" v-on="object" -->
    <Child v-bind="$attrs" v-on="$listeners"></Child>
    
  • v-model

    <el-input v-model="keyword"></el-input>
    <!--- 等价下面这个 -->
    <el-input :value="keyword" @input="keyword = $event"></el-input>
    
Vue.directive("指令名", {
  // 生命周期
  // 只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
  bind(el, binding, vnode, oldVnode) {
    //
    // binging.value 拿到指令值
    // binding.modifiers 修饰符对象
  },
  // 被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)
  inserted() {},
  update() {},
  componentUpdated() {},
  // 只调用一次,指令与元素解绑时调用
  unbind() {},
});

// 默认绑定 bind update 的生命周期
Vue.directive("指令名", function (el, binding, vnode, oldVnode) {});

修饰符

  • .lazy、.number、.trim、.enter、.prevent、.self

  • .sync

    <Dialog  :visible.sync="visible"></Child>
    <!--- 等价下面这个 -->
    <Dialog  :visible="visible" @update:visible="visible = $event"></Child>
    

scoped

加了 scoped 就只作用于当前组件

<style scoped></style>

渲染规则

.a .b {
}
== > .a .b[data-v-xx] {
}
.a /deep/ .b {
}
== > .a[data-v-xxx] .b {
}
.a >>> .b {
}
== > .a[data-v-xxx] .b {
}
.a ::v-deep .b {
}
== > .a[data-v-xxx] .b {
}

vue-router

// 全局路由守卫
router.beforeEach((to, from, next) => {})
router.afterEach((to, from) => {})

new VueRouter({
    mode: 'hash', // hash | history | abstract
    // 滚动位置
    scrollBehavior(to, from, savedPosition) {
       if (savedPosition) return savedPosition
       return { y: 0 }
	},
    routes: [
        {
            path: '/',
            // 路由独享守卫
            beforeEnter(to, from, next) {}
        }
    ]
})

// 组件内的路由
beforeRouteEnter(to, from, next) {}
beforeRouteUpdate(to, from, next) {}
beforeRouteLeave(to, from, next) {}

// 跳转
this.$router.push({name: '', path: '', query: {}})
// 路由信息
this.$route.query this.$route.params

vuex

state getters mutations actions modules

// state
this.$store.state.userInfo;
// getters
this.$store.getters.userInfo;

// mutations
this.$store.commit("SET_USER_INFO", "传递数据");

// actions
this.$store.dispatch("logout").then((res) => {});

// -----------------------------------
// modules > user
// namespaced: true,

// state 拿 name
this.$store.state.user.avatar;
// getters
this.$store.getters.user.avatar;

// mutations
this.$store.commit("user/SET_TOKEN", "传递数据");

// actions
this.$store.dispatch("user/login").then((res) => {});

// -----------------------------------
// modules > user
// namespaced: false,

// state 拿 name
this.$store.state.user.avatar;
// getters
this.$store.getters.user.avatar;

// mutations
this.$store.commit("SET_TOKEN", "传递数据");

// actions
this.$store.dispatch("login").then((res) => {});

辅助函数

mapState, mapGetters, mapMutations, mapActions;

vue3

Vue3 的 8 种和 Vue2 的 12 种组件通信,值得收藏

聊一聊 Vue3 的 9 个知识点

Vue3 有哪些变化

  • 新增了三个组件:Fragment 支持多个根节点、Suspense 可以在组件渲染之前的等待时间显示指定内容、Teleport 可以让子组件能够在视觉上跳出父组件(如父组件 overflow:hidden)
  • 新增指令 v-memo,可以缓存 html 模板,比如 v-for 列表不会变化的就缓存,简单说就是用内存换时间
  • 支持 Tree-Shaking,会在打包时去除一些无用代码,没有用到的模块,使得代码打包体积更小
  • 新增 Composition API 可以更好的逻辑复用和代码组织,同一功能的代码不至于像以前一样太分散,虽然 Vue2 中可以用 minxin 来实现复用代码,但也存在问题,比如方法或属性名会冲突,代码来源也不清楚等
  • Proxy 代替 Object.defineProperty 重构了响应式系统,可以监听到数组下标变化,及对象新增属性,因为监听的不是对象属性,而是对象本身,还可拦截 apply、has 等 13 种方法
  • 重构了虚拟 DOM,在编译时会将事件缓存、将 slot 编译为 lazy 函数、保存静态节点直接复用(静态提升)、以及添加静态标记、Diff 算法使用 最长递增子序列 优化了对比流程,使得虚拟 DOM 生成速度提升 200%
  • 支持在 <style></style> 里使用 v-bind,给 CSS 绑定 JS 变量(color: v-bind(str))
  • setup 代替了 beforeCreate 和 created 这两个生命周期
  • 新增了开发环境的两个钩子函数,在组件更新时 onRenderTracked 会跟踪组件里所有变量和方法的变化、每次触发渲染时 onRenderTriggered 会返回发生变化的新旧值,可以让我们进行有针对性调试
  • 毕竟 Vue3 是用 TS 写的,所以对 TS 的支持度更好
  • Vue3 不兼容 IE11

vue3 生命周期

选项式 APIHook inside setup
beforeCreateNot needed*
createdNot needed*
beforeMountonBeforeMount
mountedonMounted
beforeUpdateonBeforeUpdate
updatedonUpdated
beforeUnmountonBeforeUnmount
unmountedonUnmounted
errorCapturedonErrorCaptured
renderTrackedonRenderTracked
renderTriggeredonRenderTriggered
activatedonActivated
deactivatedonDeactivated

基本代码

main.js

// main.js
import { createApp } from "vue";
import App from "./App.vue";

import HelloWorld from "./components/HelloWorld.vue";
const app = createApp(App);

// 全局组件
app.component("HelloWorld", HelloWorld);

// 全局属性
// vue2.0 Vue.prototype.$http
app.config.globalProperties.$http = () => {
  console.log("http ==");
};

app.mount("#app");

App.vue

<!--- App.vue  -->
<template>
  <div>
    <!-- v-model="xxx"  <==> v-model:modelValue="xxx" -->
    <!-- :value="xxx" @input="xxx = $event" -->
    <!-- value $emit('input', '传递') -->

    <!-- 
      visible.sync="visible" 
      ==>
      :visible="visible" @update:visible="visible = $event"
      -->

    <!-- vue3 把 .sync 去掉,==> 
        v-model:visible="visible"
       -->

    <!--     
    <div :ref="setDivRef">
      count: {{ count }}
      <p>
        <button @click="add">+</button>
        <button @click="reduce">-</button>
      </p>
    </div>

    <ul>
      <li>姓名:{{ user.name }}</li>
      <li>年龄:{{ user.age }}</li>
    </ul> -->

    <!-- v-model="num" -->
    <Child
      title="父组件传递的title"
      :modelValue="num"
      @update:modelValue="num = $event"
      @change="onChildChange"
      v-model:visible="visible"
      ref="childRef"
    ></Child>
    <!-- <HelloWorld></HelloWorld> -->
  </div>
</template>

<script>
  import Child from "./Child-setup.vue";
  import { reactive, ref } from "@vue/reactivity";
  import { onMounted, provide } from "@vue/runtime-core";
  export default {
    components: { Child },
    // data() {
    //   return {
    //     msg: '哈哈哈',
    //   }
    // },
    setup() {
      const msg = ref("哈哈哈2"); // => reactive({value: 哈哈哈2 })
      const obj = ref({ x: "xx" });
      console.log(" obj.value: ", obj.value);
      const user = reactive({ name: "张三", age: 18 });
      const count = ref(0);

      provide("count", count);
      provide("http", () => {
        console.log("$http >>>");
      });

      const add = () => {
        count.value++;
      };

      const reduce = () => {
        count.value--;
      };

      const num = ref(1);
      const visible = ref(false);

      // this.$refs.childRef x
      // refs
      // 1. 用字符串
      const childRef = ref(null);
      onMounted(() => {
        console.log(" childRef.value: ", childRef.value);
      });

      let divRef;
      const setDivRef = (el) => {
        console.log(" el: ", el);
        divRef = el;
      };

      return {
        msg,
        user,
        count,
        add,
        reduce,
        num,
        visible,
        childRef,
        setDivRef,
      };
    },

    methods: {
      onChildChange() {},
    },
  };
</script>

<style>
  #app {
    font-family: Avenir, Helvetica, Arial, sans-serif;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    text-align: center;
    color: #2c3e50;
    margin-top: 60px;
  }
</style>

Child-composition (组合式 api)

<template>
  <!-- 
  1. 多个片段, 多个根标签 
  2. v-for v-if 优先级变化 v3 v-if > v-for
-->
  <div>
    <button @click="triggerEvent">触发事件</button>

    <div>num2:{{ num2 }}</div>
    <div>count:{{ count }}</div>

    modelValue:{{ modelValue }}
    <button @click="add">+</button>
    <hr />
    visible:{{ visible }}
    <button @click="updateVisible">更新visible</button>

    <!--   -->
    <teleport to="body">
      <div v-if="visible">对话框</div>
    </teleport>
  </div>
</template>

<script>
  import {
    computed,
    inject,
    onActivated,
    onBeforeMount,
    onBeforeUnmount,
    onBeforeUpdate,
    onDeactivated,
    onMounted,
    onUnmounted,
    onUpdated,
    watch,
    watchEffect,
  } from "@vue/runtime-core";
  export default {
    props: {
      title: String,
      modelValue: Number,
      visible: Boolean,
    },
    // computed: {
    //   num2() {
    //     return this.modelValue * 2
    //   }
    // },
    emits: ["change", "update:modelValue", "update:visible"],
    // 发生在 beforeCreate
    // attrs 除了 class style,props 之外的属性
    //

    // watch: {
    //   title: {
    //     deep: true, // 深度简单

    //   }
    // },
    // 组合式API(composition),  选项式API(options)
    setup(props, { emit, attrs, slots }) {
      console.log(" attrs: ", attrs);
      console.log(" props: ", props);

      // computed
      const num2 = computed(() => props.modelValue * 2);
      // const num2 = computed({
      //   get: () => props.modelValue * 2,
      //   set: (val) => {
      //     ssss
      //   }
      // })

      //
      const count = inject("count");
      console.log(" count: ", count);

      // watch
      // this.$watch()
      const unwatch = watch(
        () => props.modelValue,
        (newVal, oldValue) => {
          console.log(" newVal: ", newVal);
          if (newVal >= 10) {
            // 取消监听
            unwatch();
          }
        },
        {
          deep: true,
          // immediate: true
        }
      );

      // 自动收集依赖,所以会初始化的时候就执行一次
      watchEffect(() => {
        console.log(" props.modelValue: ", props.modelValue);
      });

      // hooks
      onBeforeMount(() => {});
      onMounted(() => {
        console.log("哈哈哈");
      });
      onBeforeUpdate(() => {});
      onUpdated(() => {});
      onBeforeUnmount(() => {});
      onUnmounted(() => {});

      // keep-alive
      onActivated(() => {});
      onDeactivated(() => {});

      // methods
      const triggerEvent = () => {
        emit("change", "传递的数据");
      };

      const add = () => {
        emit("update:modelValue", props.modelValue + 1);
      };
      const updateVisible = () => {
        console.log(" props.visible: ", props.visible);
        emit("update:visible", !props.visible);
      };

      return {
        triggerEvent,
        add,
        updateVisible,
        num2,
        count,
      };
    },
    // beforeCreate() {
    //   console.log('beforeCreate')
    // },
    // created() {
    //   console.log('created')
    // },

    // beforeDestroy beforeUnmount
    // destroyed unmounted
  };
</script>

Child-setup

<template>
  <div>
    <p>title: {{ title }}</p>
    <p>num2: {{ num2 }}</p>
    <p>count: {{ count }}</p>

    <div>
      modelValue:{{ modelValue }}
      <button @click="add">+</button>
    </div>

    <button @click="triggerEvent">触发事件</button>

    <!-- <input type="text" v-model="inputValue"> -->
    <!--   -->
    <input type="text" :value="inputValue" @input="onInputUpdate" />

    <!-- volar -->
    <Foo></Foo>
  </div>
</template>

<!--- vue 3.2.x -->
<script setup>
  import {
    computed,
    getCurrentInstance,
    inject,
    ref,
    useAttrs,
    useSlots,
  } from "@vue/runtime-core";
  import Foo from "./foo.vue";

  // props
  const props = defineProps({
    title: String,
    modelValue: Number,
  });
  // computed
  const num2 = computed(() => props.modelValue * 2);
  const count = inject("count");

  // emit
  const emit = defineEmits(["change", "update:modelValue", "update:visible"]);
  const triggerEvent = () => {
    emit("change", "传递的数据");
  };
  const add = () => {
    emit("update:modelValue", props.modelValue + 1);
  };

  // 向父组件暴露自己的属性和方法
  defineExpose({
    num2,
    test() {
      console.log("888");
    },
  });

  const attrs = useAttrs();
  console.log(" attrs: ", attrs);
  const solts = useSlots();

  const ctx = getCurrentInstance();

  const http = ctx.appContext.config.globalProperties.$http;
  http("xxx");

  const $http = inject("http");
  $http();

  // $ref: ref(false)

  const inputValue = ref("");

  const onInputUpdate = (event) => {
    console.log(" event: ", event);
    inputValue.value = event.target.value;
  };
</script>

项目相关

git 常用命令

shfshanyue.github.io/cheat-sheet…

Webpack 一些核心概念:

【万字】透过分析 webpack 面试题,构建 webpack5.x 知识体系

  • Entry:入口,指示 Webpack 应该使用哪个模块,来作为构建其内部 依赖图(dependency graph) 的开始。

  • Output:输出结果,告诉 Webpack 在哪里输出它所创建的 bundle,以及如何命名这些文件。

  • Module:模块,在 Webpack 里一切皆模块,一个模块对应着一个文件。Webpack 会从配置的 Entry 开始递归找出所有依赖的模块。

  • Chunk:代码块,一个 Chunk 由多个模块组合而成,用于代码合并与分割。

  • Loader:模块代码转换器,让 webpack 能够去处理除了 JS、JSON 之外的其他类型的文件,并将它们转换为有效 模块,以供应用程序使用,以及被添加到依赖图中。

  • Plugin:扩展插件。在 webpack 运行的生命周期中会广播出许多事件,plugin 可以监听这些事件,在合适的时机通过 webpack 提供的 api 改变输出结果。常见的有:打包优化,资源管理,注入环境变量。

  • Mode:模式,告知 webpack 使用相应模式的内置优化


  • hash: 每次构建的生成唯一的一个 hash,且所有的文件 hash 串是一样的
  • chunkhash: 每个入口文件都是一个 chunk,每个 chunk 是由入口文件与其依赖所构成,异步加载的文件也被视为是一个 chunk, chunkhash是由每次编译模块,根据模块及其依赖模块构成 chunk 生成对应的 chunkhash, 这也就表明了每个 chunk 的 chunkhash 值都不一样, 也就是说每个 chunk 都是独立开来的,互不影响,每个 chunk 的更新不会影响其他 chunk 的编译构建

  • contenthash:由文件内容决定,文件变化 contenthash 才会变化,一般配合 mini-css-extract-plugin插件提取出 css

    const MiniCssExtractPlugin = require("mini-css-extract-plugin");
    const HTMLWebpackPlugin = require("html-webpack-plugin");
    
    module.exports = {
      // ...
      module: {
        rules: [
          {
            test: /\.css$/,
            use: [
              {
                // 把 style-loader替换掉,不要使用 style-loader了
                loader: MiniCssExtractPlugin.loader,
                options: {
                  outputPath: "css/",
                },
              },
              "css-loader",
            ],
          },
        ],
      },
      plugins: [
        // ....
        new MiniCssExtractPlugin({
          filename: "css/[name].[contenthash].css",
        }),
      ],
    };
    

提升 webpack 打包速度

一套骚操作下来,webpack 项目打包速度飞升