端面试题整理
已同步到掘金、CSDN
掘金地址: https://juejin.cn/post/7075332630417244173
CSDN 地址:https://blog.csdn.net/z1832729975/article/details/123431083
个人整理了很多网上常见的面试题,希望也能通过这来复习
内容有点多,可能 CSDN 上预览效果不好,想要 markdown 文档的可以私信我,推荐使用Typora
看
比较好的面试题
2021 年我的前端面试准备
2021 年前端面试必读文章【超三百篇文章/赠复习导图】
CSS、HTML
浏览器内核
IE: trident 内核
Firefox:gecko 内核
Safari: webkit 内核
Opera: 以前是 presto 内核,Opera 现已改用 GoogleChrome 的 Blink 内核
Chrome: Blink(基于 webkit,Google 与 Opera Software 共同开发)
你是怎么理解 HTML 语义化
HTML 语义化简单来说就是用正确的标签来做正确的事。
比如表示段落用 p 标签、表示标题用 h1-h6 标签、表示文章就用 article 等。
DOCTYPE 的作用
-
<!DOCTYPE>
声明位于文档中的最前面,处于<html>
标签之前。告知浏览器以何种模式来渲染文档。 -
严格模式的排版和 JS 运作模式是 以该浏览器支持的最高标准运行。
-
在混杂模式中,页面以宽松的向后兼容的方式显示。模拟老式浏览器的行为以防止站 点无法工作。
-
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
,他们都是文字类块级标签,里面不能放其他块级元素。
meta viewport 是做什么用的,怎么写
使用目的
告诉浏览器,用户在移动端时如何缩放页面
<meta
name="viewport"
content="width=device-width,initial-scale=1,maximum-scale-1,minimum-scale=1"
/>
with=device-width
将布局视窗(layout viewport)的宽度设置为设备屏幕分辨率的宽度
initial-scale=1
页面初始缩放比例为屏幕分辨率的宽度
maximum-scale=1
指定用户能够放大的最大比例
minimum-scale=1
指定用户能够缩小的最大比例
label 标签的作用
label 标签来定义表单控制间的关系,当用户选择该标签时,浏览器会自动将焦点转到和标签相关的表单控件上。
<label for="Name">Number:</label>
<input type="text" name="Name" id="Name" />
<label
>Date:
<input type="text" name="B" />
</label>
canvas 在标签上设置宽高 和在 style 中设置宽高有什么 区别
canvas 标签的 width 和 height 是画布实际宽度和高度,绘制的图形都是在这个上面。
而 style 的 width 和 height 是 canvas 在浏览器中被渲染的高度和宽度。
如果 canvas 的 width 和 height 没指定或值不正确,就被设置成默认值 。
html5 新特性
- 语义化标签, header,footer,nav,aside,article,section
- 增强型表单
- 视频 video 和音频 audio
- Canvas 绘图
- SVG绘图
- 地理定位
- 拖放 API
- WebWorker
- WebStorage( 本地离线存储 localStorage、sessionStorage )
- WebSocket
css3 新特性
1、圆角效果;2、图形化边界;3、块阴影与文字阴影;4、使用 RGBA 实现透明效果;5、渐变效果;6、使用“@Font-Face”实现定制字体;7、多背景图;8、文字或图像的变形处理;9、多栏布局;10、媒体查询等。
1、颜色:新增RGBA、HSLA模式
2、文字阴影:(text-shadow)
3、边框:圆角(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
11、2D转换:transform:translate(x,y) rotate(x,y)旋转 skew(x,y)倾斜 scale(x,y)缩放
12、3D转换
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
解决什么问题
-
可以用来解决两栏布局
BFC 的区域不会与 float box 重叠
.left { float: flet; } .right { overflow: hidden; }
-
解决 margin 塌陷和 margin 合并问题
-
解决浮动元素无法撑起父元素
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 值的大小
- css3 媒体查询 @media screen and(max-width: 750px){}
5.vw+vh
6.使用一些框架(bootstrap,vant)
什么是响应式设计:响应式网站设计是一个网站能够兼容多个终端,智能地根据不同设备环境进行相对应的布局
响应式设计的基本原理:基本原理是通过媒体查询检测不同的设备屏幕尺寸设置不同的 css 样式 页面头部必须有 meta 声明的
布局
- 两栏布局,左边定宽,右边自适应
- 三栏布局、圣杯布局、双飞翼布局
水平垂直居中
方法一:给父元素设置成弹性盒子,子元素横向居中,纵向居中
方法二:父相子绝后,子部分向上移动本身宽度和高度的一半,也可以用 transfrom:translate(-50%,-50%)(最常用方法)
方法三:父相子绝,子元素所有定位为 0,margin 设置 auto 自适应
iframe 有哪些缺点?
iframe 是一种框架,也是一种很常见的网页嵌入方
iframe 的优点:
- iframe 能够原封不动的把嵌入的网页展现出来。
- 如果有多个网页引用 iframe,那么你只需要修改 iframe 的内容,就可以实现调用的每一个页面内容的更改,方便快捷。
- 网页如果为了统一风格,头部和版本都是一样的,就可以写成一个页面,用 iframe 来嵌套,可以增加代码的可重用。
- 如果遇到加载缓慢的第三方内容如图标和广告,这些问题可以由 iframe 来解决。
iframe 的缺点:
- 会产生很多页面,不容易管理。
- iframe 框架结构有时会让人感到迷惑,如果框架个数多的话,可能会出现上下、左右滚动条,会分散访问者的注意力,用户体验度差。
- 代码复杂,无法被一些搜索引擎索引到,这一点很关键,现在的搜索引擎爬虫还不能很好的处理 iframe 中的内容,所以使用 iframe 会不利于搜索引擎优化。
- 很多的移动设备(PDA 手机)无法完全显示框架,设备兼容性差。
- iframe 框架页面会增加服务器的 http 请求,对于大型网站是不可取的。现在基本上都是用 Ajax 来代替 iframe,所以 iframe 已经渐渐的退出了前端开发。
link @import 导入 css
link 是 XHTML 标签,除了加载 CSS 外,还可以定义 RSS 等其他事务;
@import 属于 CSS 范畴, 只能加载 CSS。
link 引用 CSS 时,在页面载入时同时加载;@import 需要页面网页完全载入以后加载。link
无兼容问题;@import 是在 CSS2.1 提出的,低版本的浏览器不支持。
link 支持使用 Javascript 控制 DOM 去改变样式;而@import 不支持。
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
);
事件委托
简介:事件委托指的是,不在事件的发生地(直接 dom)上设置监听函数,而是
在其父元素上设置监听函数,通过事件冒泡,父元素可以监听到子元素上事件的
触发,通过判断事件发生元素 DOM 的类型,来做出不同的响应。
举例:最经典的就是 ul 和 li 标签的事件监听,比如我们在添加事件时候,采用
事件委托机制,不会在 li 标签上直接添加,而是在 ul 父元素上添加。
好处:比较合适动态元素的绑定,新添加的子元素也会有监听函数,也可以有事
件触发机制
如果需要手动写动画,你认为最小时间间隔是多久
多数显示器默认频率是 60Hz,即 1 秒刷新 60 次,所以理论上最小间隔为
1/60*1000ms = 16.7ms
::before和:after中双冒号和单冒号有什么区别
单冒号(:)用于 CSS3 伪类,双冒号(::)用于 CSS3 伪元素。 ::before 就是以一个子元素的存在,定义在元素主体内容之前的一个伪元素。并不存在于 dom 之中,只存在在页面之中。 :before 和 :after 这两个伪元素,是在 CSS2.1 里新出现的。起初,伪元素的前缀使用的是单 冒号语法,但随着 Web 的进化,在 CSS3 的规范里,伪元素的语法被修改成使用双冒号,成 为::before ::after
CSS sprites 精灵图
CSS Sprites 其实就是把网页中一些背景图片整合到一张图片文件中,再利用 CSS 的 “background-image”,“ background-repeat ”,“ background-position”
的 组 合 进 行 背 景 定 位 , background-position 可以用数字能精确的定位出背景图片的位置。这样可以减少很多图片请 求的开销,因为请求耗时比较长;请求虽然可以并发,但是也有限制,一般浏览器都是 6 个
重排和重绘
重绘(repaint 或 redraw):当盒子的位置、大小以及其他属性,例如颜色、字 体大小等都确定下来之后,浏览器便把这些原色都按照各自的特性绘制一遍,将 内容呈现在页面上。重绘是指一个元素外观的改变所触发的浏览器行为,浏览器 会根据元素的新属性重新绘制,使元素呈现新的外观。
触发重绘的条件:改变元素外观属性。如:color,background-color 等。 注意:table 及其内部元素可能需要多次计算才能确定好其在渲染树中节点的属性值,比同等元素要多花两倍时间,这就是我们尽量避免使用 table 布局页面的 原因之一。
重排(重构/回流/reflow):当渲染树中的一部分(或全部)因为元素的规模尺寸, 布局,隐藏等改变而需要重新构建,这就称为回流(reflow)。每个页面至少需要 一次回流,就是在页面第一次加载的时候。
重绘和重排的关系:在回流的时候,浏览器会使渲染树中受到影响的部分失效, 并重新构造这部分渲染树,完成回流后,浏览器会重新绘制受影响的部分到屏幕 中,该过程称为重绘。所以,重排必定会引发重绘,但重绘不一定会引发重排。
JavaScript
js 数据类型
8 中,ES6
出的 Symbol BigInt
Number String Boolean undefined null Object Symbol BigInt
js 的基本数据类型和复杂数据类型的区别(在堆和栈中,赋值时的不同,一个拷贝值一个拷贝地址)
基本类型和引用类型在内存上存储的区别
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
属性是否在某个实例对象的原型链上, 不能判断基本数据类型
// 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
// 深拷贝
// 1. 最简单的,JSON.stringify,但这个有问题,看下面有说明
let arr2 = JSON.parse(JSON.stringify(arr));
// 2. 自己封装,要递归处理
实现深拷贝-简单版
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;
}
JSON.stringify
问题
-
如果有循环引用就报错
-
Symbol
、function
、undefined
会丢失 -
布尔值
、数字
、字符串
的包装对象会转换成原始值 -
NaN
、Infinity
变成null
-
Date
类型的日期会变成字符串 -
RegExp
、Error
被转换成了空对象{}
模块化
-
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 的区别有哪些
https://blog.csdn.net/qq_38912819/article/details/80597101
- 对于依赖的模块,AMD 是提前执行,CMD 是延迟执行。不过 RequireJS 从 2.0 开始,也改成可以延迟执行(根据写法不同,处理方式不同)
- 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 的新函数
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》
- 闭包用途:
- 能够访问函数定义时所在的词法作用域(阻止其被回收)
- 私有化变量
- 模拟块级作用域
- 创建模块
- 闭包缺点:会导致函数的变量一直保存在内存中,过多的闭包可能会导致内存泄漏
原型、原型链(高频)
原型: 对象中固有的__proto__
属性,该属性指向对象的prototype
原型属性。
原型链: 当我们访问一个对象的属性时,如果这个对象内部不存在这个属性,那么它就会去它的原型对象里找这个属性,这个原型对象又会有自己的原型,于是就这样一直找下去,也就是原型链的概念。原型链的尽头一般来说都是Object.prototype
所以这就是我们新建的对象为什么能够使用toString()
等方法的原因。
特点: JavaScript
对象是通过引用来传递的,我们创建的每个新对象实体中并没有一份属于自己的原型副本。当我们修改原型时,与之相关的对象也会继承这一改变。
this 指向、new 关键字
this
对象是是执行上下文中的一个属性,它指向最后一次调用这个方法的对象,在全局函数中,this
等于window
,而当函数被作为某个对象调用时,this 等于那个对象。 在实际开发中,this
的指向可以通过四种调用模式来判断。
- 函数调用,当一个函数不是一个对象的属性时,直接作为函数来调用时,
this
指向全局对象。 - 方法调用,如果一个函数作为一个对象的方法来调用时,
this
指向这个对象。 - 构造函数调用,
this
指向这个用new
新创建的对象。 - 第四种是
apply 、 call 和 bind
调用模式,这三个方法都可以显示的指定调用函数的 this 指向。apply
接收参数的是数组,call
接受参数列表,`` bind方法通过传入一个对象,返回一个
this绑定了传入对象的新函数。这个函数的
this指向除了使用
new `时会被改变,其他情况下都不会改变。
new
- 首先创建了一个新的空对象
- 设置原型,将对象的原型设置为函数的
prototype
对象。 - 让函数的
this
指向这个对象,执行构造函数的代码(为这个新对象添加属性) - 判断函数的返回值类型,如果是值类型,返回创建的对象。如果是引用类型,就返回这个引用类型的对象。
// 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。
ES6
- 新增 Symbol 类型 表示独一无二的值,用来定义独一无二的对象属性名;
- const/let 都是用来声明变量,不可重复声明,具有块级作用域。存在暂时性死区,不存在变量提升。(const 一般用于声明常量);
- 变量的解构赋值(包含数组、对象、字符串、数字及布尔值,函数参数),剩余运算符(…rest);
- 模板字符串(
${data}
); -
...
扩展运算符(数组、对象);; - 箭头函数;
- Set 和 Map 数据结构;
- Proxy/Reflect;
- Promise;
- async 函数;
- Class;
- Module 语法(import/export)。
let/const
const
声明一个只读的常量。一旦声明,常量的值就不能改变 https://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" } };
-
?.
可选链运算符左侧的对象是否为
null
或undefined
。如果是的,就不再往下运算,而是返回undefined
a?.b; // 等同于 a == null ? undefined : a.b; // 注意 undefined == null ==> true
-
??
Null 判断运算符
https://es6.ruanyifeng.com/#docs/operator#Null-%E5%88%A4%E6%96%AD%E8%BF%90%E7%AE%97%E7%AC%A6
const headerText = response.settings.headerText ?? "Hello,world!";
const animationDuration = response.settings.animationDuration ?? 300;
const showSplashScreen = response.settings.showSplashScreen ?? true;
但左侧的为 undefined
或者null
是就返回右边的,否则就直接返回左边的
箭头函数和普通函数的区别
- 箭头函数没有
this
,this
是继承于当前的上下文,不能通过call
,apply
,bind
去改变 this - 箭头函数没有自己的
arguments
对象,但是可以访问外围函数的arguments
对象 - 不能通过
new
关键字调用(不能作为构造函数),同样也没有new.target
和原型
如何解决异步回调地狱
promise、generator、async/await
mouseover 和 mouseenter 的区别
mouseover:当鼠标移入元素或其子元素都会触发事件,所以有一个重复触发,
冒泡的过程。对应的移除事件是 mouseout
mouseenter:当鼠标移除元素本身(不包含元素的子元素)会触发事件,也就是
不会冒泡,对应的移除事件是 mouseleave
setTimeout、setInterval 和 requestAnimationFrame 之间的区别
与 setTimeout 和 setInterval 不同,requestAnimationFrame 不需要设置时间 间隔, 大多数电脑显示器的刷新频率是 60Hz,大概相当于每秒钟重绘 60 次。大多数浏 览器都会对重绘操作加以限制,不超过显示器的重绘频率,因为即使超过那个频 率用户体验也不会有提升。因此,最平滑动画的最佳循环间隔是 1000ms/60,约 等于 16.6ms。 RAF 采用的是系统时间间隔,不会因为前面的任务,不会影响 RAF,但是如果前 面的任务多的话,会响应 setTimeout 和 setInterval 真正运行时的时间间隔。 特点:
(1)requestAnimationFrame 会把每一帧中的所有 DOM 操作集中起来,在一次 重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率。
(2)在隐藏或不可见的元素中,requestAnimationFrame 将不会进行重绘或回 流,这当然就意味着更少的 CPU、GPU 和内存使用量
(3)requestAnimationFrame 是由浏览器专门为动画提供的 API,在运行时浏览 器会自动优化方法的调用,并且如果页面不是激活状态下的话,动画会自动暂停, 有效节省了 CPU 开销。
vue
vue2 是通过Object.defineProperty
来实现响应式的,所以就会有一些缺陷
- 当修改一个对象的某个键值属性时,当这个键值没有在这个对象中,vue 不能做响应式处理
- 但直接修改数组的某一项(
arr[index]='xxx'
)vue 不能做响应式处理
可用下面的解决响应式
- Vue.set ==> this.$set(对象\数组, key 值、index, value)
- 修改数组
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 的差不多 https://www.bilibili.com/video/BV1wy4y1D7JT?p=48
…待更新
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: ",
//
// obj: {
// deep: 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
(vue2v-for
比v-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, 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, savedPosition) {
if (savedPosition) return savedPosition
return { y: 0 }
},
routes: [
{
path: '/',
// 路由独享守卫
beforeEnter(to, next) {}
}
]
})
// 组件内的路由
beforeRouteEnter(to, next) {}
beforeRouteUpdate(to, next) {}
beforeRouteLeave(to, 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 有哪些变化
- 新增了三个组件:
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 生命周期
选项式 API | Hook inside setup
|
---|---|
beforeCreate |
Not needed* |
created |
Not needed* |
beforeMount |
onBeforeMount |
mounted |
onMounted |
beforeUpdate |
onBeforeUpdate |
updated |
onUpdated |
beforeUnmount |
onBeforeUnmount |
unmounted |
onUnmounted |
errorCaptured |
onErrorCaptured |
renderTracked |
onRenderTracked |
renderTriggered |
onRenderTriggered |
activated |
onActivated |
deactivated |
onDeactivated |
基本代码
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,
updateVisible,
num2,
// 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,
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: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
常用命令
https://shfshanyue.github.io/cheat-sheets/git
git pull 和 git featch 的区别
怎么样进行合并,比如把 mater 分支合并到 dev 分支
Webpack 一些核心概念:
-
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
插件提取出 cssconst 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", }), ], };