这个有点长…慎点这份资料是个人对最近前端八股文的总结,资料有来源网络,MDN,还有黑马程序员的进阶部分.在vue的原理部分,采用的的<<vue设计与实现>>(霍春阳著)
这本书.
这是vue官方团队出版,我想对原理的解释应该不会有人比他们还懂. 但看书实在是发困,有点回到了第一次学C++ primer plus的时候,
看完了吗? 看完了. 看懂了吗 ? 可以说完全没懂. 所以我想通过写笔记的方式,来提高自己对前端的理解.
HMTL和CSS部分比较简洁.主要是理解容易,写起来也不难.JavaScript的部分其实还有很多,比如手写promise.
但已经写太多了,因此我想就放在以后再写.现在想转看看vue,毕竟书看完了,还没看懂.趁着现在还有点印象
二刷一遍.
有个悲剧的事情,忘记搞图床了,图片无了.但应该是不影响阅读的.
第一篇: HTML
1. 对HTML语义化的理解
语义化是指对内容的结构化,选择合适的标签(代码语义化).通俗来说,用正确的标签做正确的事.
比如
<header></header> 头部
<nav></nav> 导航栏
<section></section> 区块(有语义化的div)
<main></main> 主要区域
<article></article> 主要内容
<aside></aside> 侧边栏
<footer></footer> 底部
2. H5的更新
-
语义化标签
-
header: 定义文档的页眉.
- nav: 定义导航链接部分.
- footer: 定义文档或页脚.
- article: 定义文字内容.
- section: 定义一个区域.
-
aside: 定义内容之外的侧边.
-
媒体标签
-
audio标签
html
<audio src='' controls autoplay loop='true'></audio>
属性:
- controls 控制面板
- autoplay 自动播放
-
loop=true 循环播放
-
表单:可选功能多,操作简单.
-
Web 存储:
提供两种在客户端存储数据的新方法
a. localStorage – 没有时间限制的数据存储.
b. sessionStorage -- 针对一个session的数据存储
- 其他:
canvas标签,SVG矢量图.
3. 行内元素有哪些.块级元素有哪些.空元素有哪些?
行内元素: a b span img input select
这些元素在创建时不占有独立的区域,且不能设置高度,宽度,对齐等,仅仅只靠内容或图形尺寸来支撑结构.
块级元素: div ul ol h1 h2 h3 ... h6 p
空元素: 没有内容的HTML元素常见有<br> <hr> <input> <link>
第二篇: CSS
1. 选择器及其优先级
选择器 | 格式 | 优先级权重 |
---|---|---|
id选择器 | #id | 100 |
类选择器 | #classname | 10 |
属性选择器 | a[ref=“eee”] | 10 |
伪类选择器 | li:last-child | 10 |
标签选择器 | div | 1 |
伪元素选择器 | li:after | 1 |
相邻兄弟选择器 | h1+p | 0 |
子选择器 | ul>li | 0 |
后代选择器 | li a | 0 |
通配符选择器 | * | 0 |
需要注意的是, !important
声明的样式的优先级最高;
优先级同级时,最后出现的生效.继承得到的样式优先级最低(这个理解可以参考java,应该很容易理解它的设计意图)
通用选择器(*),子选择器(>),相邻兄弟选择器(+)并不在四个等级中,所以权值为0.
2. display的属性值和作用.
属性值 | 作用 |
---|---|
none | 元素不显示,并且会从文档流中移除。 |
block | 块类型。默认宽度为父元素宽度,可设置宽高,换行显示。 |
inline | 行内元素类型。默认宽度为内容宽度,不可设置宽高,同行显示。 |
inline-block | 默认宽度为内容宽度,可以设置宽高,同行显示。 |
list-item | 像块类型元素一样显示,并添加样式列表标记。 |
table | 此元素会作为块级表格来显示。 |
inherit | 规定应该从父元素继承display属性的值。 |
注意:
- block 是将元素变成块元素,会像div一样直接占用一整行. 可以设置宽高.
- inline设置成行内元素,像span,a标签等等.
- inline-block 比较特殊,它可以放在同一行显示,又可以设置宽高.
3. 隐藏元素的方法
- display:none 渲染树直接不渲染该对象,直接从文档流移除
- visibility: hidden 仍然会渲染,但不会响应绑定的监听事件(如点击)
- opacity: 0 透明度为0.欺骗自己的眼睛
- position: absolute,并通过移动将元素移出可视区域.
- z-index,设置为比所有元素都小的负值. 这样就被其他元素给覆盖住了
- transform: scale(0,0): 将元素缩放至0,元素就被隐藏了.
4. 伪元素和伪类的区别和作用
伪元素: 在内容元素的前后插入额外的元素或样式,它不会在dom 的源码上看见.但它确实被展示了. 因此就叫做伪元素.
p::before {content:"第一章:";}
p::after {content:"Hot!";}
p::first-line {background:red;}
p::first-letter {font-size:30px;}
伪类: 将一些特殊的效果添加到特定选择器上,是在已有元素上添加类别,不会产生新的元素.
a:hover {color: #FF00FF}
p:first-child {color: red}
总结 伪类是通过在元素选择器上添加伪类改变状态,伪元素通过对元素的操作进行改变.
5. 盒模型
6. CSS3 中的新特性
- 新的选择器.
- 圆角(border-radius: 8px)
- 多列布局(multi-column layout)
7. 为什么在移动端开发需用用到@3x,@2x这种图片
以 iPhone XS 为例,当写 CSS 代码时,针对于单位 px,其宽度为 414px & 896px,也就是说当赋予一个 DIV元素宽度为 414px,这个 DIV 就会填满手机的宽度;
而如果有一把尺子来实际测量这部手机的物理像素,实际为 1242*2688 物理像素;经过计算可知,1242/414=3,也就是说,在单边上,一个逻辑像素=3个物理像素,就说这个屏幕的像素密度为 3,也就是常说的 3 倍屏。对于图片来说,为了保证其不失真,1 个图片像素至少要对应一个物理像素,假如原始图片是 500300 像素,那么在 3 倍屏上就要放一个 1500900 像素的图片才能保证 1 个物理像素至少对应一个图片像素,才能不失真。
8. margin和padding的使用场景
需用在border外侧添加空白,且空白处不需要背景色,用margin.
需用在border内测添加空白,则空白处不需要使用背景色,用padding.
9.line-height 的理解和赋值方式
line-height的概念:
一行文本的高端,包含了字间距.
10. CSS工程化的理解
工程化是为了解决以下问题.
- 宏观设计: CSS代码如何组织,拆分,模块结构怎样设计
- 编码优化: 写出更好的CSS
- 构建: 如何处理我的CSS,才能让它的打包结果最优.
- 可维护性: 代码写完了,如何最小化后续的变更成本? 如何确保任何一个开发人员都可以轻松接手?
现在有三种方向:
- 预处理器: scss | less等
- 工程化插件: PostCss
- webpack loader
11. z-index属性什么情况下会失效.
通常 z-index 的使用是在有两个重叠的标签,在一定的情况下控制其中一个在另一个的上方或者下方出现。z-index值越大就越是在上层。z-index元素的position属性需要是relative,absolute或是fixed。
z-index属性在下列情况下会失效:
- 父元素position为relative时,子元素的z-index失效。解决:父元素position改为absolute或static;
- 元素没有设置position属性为非static属性。解决:设置该元素的position属性为relative,absolute或是fixed中的一种;
- 元素在设置z-index的同时还设置了float浮动。解决:float去除,改为display:inline-block;
12. 定位与浮动
属性值 | 概述 |
---|---|
absolute | 生成绝对定位的元素,相对于static定位以外的一个父元素进行定位。元素的位置通过left、top、right、bottom属性进行规定。 |
relative | 生成相对定位的元素,相对于其原来的位置进行定位。元素的位置通过left、top、right、bottom属性进行规定。 |
fixed | 生成绝对定位的元素,指定元素相对于屏幕视⼝(viewport)的位置来指定元素位置。元素的位置在屏幕滚动时不会改变,⽐如回到顶部的按钮⼀般都是⽤此定位⽅式。 |
static | 默认值,没有定位,元素出现在正常的文档流中,会忽略 top, bottom, left, right 或者 z-index 声明,块级元素从上往下纵向排布,⾏级元素从左向右排列。 |
inherit | 规定从父元素继承position属性的值 |
13. absolute和fiexed的异同
共同点:
- 都脱离了文档流
- 覆盖非定位文档元素
- 改变了行内元素的呈现方式,将display设置为inline-block
不同点:
1.absolute的根元素可以设置, fiexed的根元素的浏览器
14. sticky定位的理解
sticky 英文字面意思是粘贴,所以可以把它称之为粘性定位。语法:position: sticky; 基于用户的滚动位置来定位。
粘性定位的元素是依赖于用户的滚动,在 position:relative 与 position:fixed 定位之间切换。它的行为就像 position:relative; 而当页面滚动超出目标区域时,它的表现就像 position:fixed;,它会固定在目标位置。元素定位表现为在跨越特定阈值前为相对定位,之后为固定定位。这个特定阈值指的是 top, right, bottom 或 left 之一,换言之,指定 top, right, bottom 或 left 四个阈值其中之一,才可使粘性定位生效。否则其行为与相对定位相同。
第三篇: JavaScript
一. 数据类型
JavaScript 有八种数据,有undefined,NULL,Boolean,Number,String,Object,Symbol,BigInt.
其中后两个是ES6新增类型.
Symbol创建后独一无二且重复性不可变,为了解决全局变量冲突的问题,这里要提一下const,二者有三个区别.
- 变量定义和重复性
const a = 10;
console.log(a); // 输出10
a = 20; // 报错,不允许重新定义
Symbol('a'); // { [Symbol()]: 'a' }
Symbol('a'); // { [Symbol()]: 'a' }
console.log(Symbol('a') == Symbol('a')); // false
- 内存原理
const定义的变量,只在声明时存储一次.之后只读不写.而symbol定义的变量,每次声明都会存储一个新的值.
- 应用场景
const在项目中,一般定义基础配置,配置参数这种一般不轻易改变的值(我们一旦定义,很长一段时间都只用这个值).
symbol定义的是唯一标识符,作为对象的唯一键值.
```JavaScript
// const的使用
const PI = 3.14159;
console.log(PI); // 输出3.14159
// Symbol的使用
let sym = Symbol();
console.log(sym); // { [Symbol()] }
sym = Symbol(‘hello’);
console.log(sym); // { [Symbol(‘hello’)]: ‘hello’ }
```
在JavaScript 的数据可以分为原始数据类型和引用数据类型. 栈存储的是原始数据类型,堆存储引用数据类型(对象,数组,函数)
引用数据类型在栈中存储了指针,解释器在寻找引用值时,先在栈中查找地址,再到堆中取值. 这个和计算机组成原理中的二级分页很相似.
所以说其实计算机大部分知识都是相同的.
操作系统中,内存被分为栈区和堆区.栈区的内存由编译器自动分配释放.堆区的内存开发者如果不释放,则在程序结束时,由垃圾回收机制释放.但存在一种强引用,使得程序结束也不会释放,这就造成了内存泄漏.
二. 数据类型检测的方式有哪些
- typeof
console.log(typeof 2); // number
console.log(typeof true); // boolean
console.log(typeof 'str'); // string
console.log(typeof []); // object
console.log(typeof function(){}); // function
console.log(typeof {}); // object
console.log(typeof undefined); // undefined
console.log(typeof null); // object
- instanceof
其运行机制是判断原型链上是否能找该类型的原型
console.log(2 instanceof Number); // false
console.log(true instanceof Boolean); // false
console.log('str' instanceof String); // false
console.log([] instanceof Array); // true
console.log(function(){} instanceof Function); // true
console.log({} instanceof Object); // true
可以看到,instanceof
只能正确判断引用数据类型,而不能判断基本数据类型。instanceof
运算符可以用来测试一个对象在其原型链中是否存在一个构造函数的 prototype
属性。
- constructor
console.log((2).constructor === Number); // true
console.log((true).constructor === Boolean); // true
console.log(('str').constructor === String); // true
console.log(([]).constructor === Array); // true
console.log((function() {}).constructor === Function); // true
console.log(({}).constructor === Object); // true
三. 判断数组的方式有哪些
原型链做判断
obj.__proto__ === Array.prototype;
ES6的Array.isArray( ) 做判断
Array.isArrray(obj);
通过instanceof做判断
obj instanceof Array
四. null和undefined
首先 Undefined 和 Null 都是基本数据类型,这两个基本数据类型分别都只有一个值,就是 undefined 和 null。
undefined 代表的含义是未定义,null 代表的含义是空对象。一般变量声明了但还没有定义的时候会返回 undefined,null主要用于赋值给一些可能会返回对象的变量,作为初始化。
undefined 在 JavaScript 中不是一个保留字,这意味着可以使用 undefined 来作为一个变量名,但是这样的做法是非常危险的,它会影响对 undefined 值的判断。我们可以通过一些方法获得安全的 undefined 值,比如说 void 0。
当对这两种类型使用 typeof 进行判断时,Null 类型化会返回 “object”,这是一个历史遗留的问题。当使用双等号对两种类型的值进行比较时会返回 true,使用三个等号时会返回 false。
五. instanceof操作符的实现原理
function func(left,right){
//获取对象的原型
let proto = Object.getPrototypeOf(left);
//获取构造函数的prototype 对象
let prototype = right.prototype;
while(true){
if(!proto) return false;
if(proto === prototype) return true;
//没有找到,就继续从原型往上找,可以联想一下并查集.
proto = Object.getPrototypeOf(proto);
}
}
六. 经典问题:0.1 + 0.2 !== 0.3 如果让它相等
计算机组成原理的经典问题.
JavaScript中只有一种数字类型,遵循IEEE 754标准,用64位固定长度表示.0.1的二进制是0.0001100110011001100...
0.2的二进制是0.00110011001100...
,由于我不太想翻书去看在754标准下怎么计算. 我们只需知道,二进制小数部分最多保留53位,
1位符号位(蓝色),11位(绿色)指数部分,52位(红色).
处理方式也很简单,
(n1 + n2).toFixed(2) // 注意,toFixed为四舍五入
七. object.assign和扩展运算法是深拷贝还是浅拷贝,两者区别
let outObj = {
inObj: {a: 1, b: 2}
}
let newObj = {...outObj}
newObj.inObj.a = 2
console.log(outObj) // {inObj: {a: 2, b: 2}}
let outObj = {
inObj: {a: 1, b: 2}
}
let newObj = Object.assign({}, outObj)
newObj.inObj.a = 2
console.log(outObj) // {inObj: {a: 2, b: 2}}
二者都是浅拷贝.
八. ES6
1. let、const、var的区别
(1)块级作用域: 块作用域由 { }
包括,let和const具有块级作用域,var不存在块级作用域。块级作用域解决了ES5中的两个问题:
- 内层变量可能覆盖外层变量
- 用来计数的循环变量泄露为全局变量
(2)变量提升: var存在变量提升,let和const不存在变量提升,即在变量只能在声明之后使用,否在会报错。
(3)给全局添加属性: 浏览器的全局对象是window,Node的全局对象是global。var声明的变量为全局变量,并且会将该变量添加为全局对象的属性,但是let和const不会。
(4)重复声明: var声明变量时,可以重复声明变量,后声明的同名变量会覆盖之前声明的遍历。const和let不允许重复声明变量。
(5)暂时性死区: 在使用let、const命令声明变量之前,该变量都是不可用的。这在语法上,称为暂时性死区。使用var声明的变量不存在暂时性死区。
(6)初始值设置: 在变量声明时,var 和 let 可以不用设置初始值。而const声明变量必须设置初始值。
(7)指针指向: let和const都是ES6新增的用于创建变量的语法。 let创建的变量是可以更改指针指向(可以重新赋值)。但const声明的变量是不允许改变指针的指向。
2. const对象属性可以修改吗?
const不是指不能动,而是指向的地址不能动.对于存在栈区的基础数据类型(数值,字符串,布尔值),其值保存在const指向的地址,因此等价于常量.
但对于引用数据来说(主要是对象和数组),在栈区仅仅保存的是一个引用指针,引用指针不能动,在堆中的数据则不是const所能控制的.
3. new一个箭头函数会怎么样
箭头函数是ES6提出的,它没有prototype,也没有this,没有arguments参数.所以不能new一个箭头函数.
new操作符实现如下:
- 创建一个对象
- 将构造函数的作用域赋值给新对象.(将对象的proto属性,指向构造函数的prototype属性)
- 将构造函数的this指向该对象.
- 返回对象
4. 箭头函数和普通函数的区别
- 箭头函数比普通函数更简洁
- 箭头函数没有自己的this,它会继承自己的上一层的this
- 箭头函数继承来的this不会改变
- call()、apply()、bind()等方法不能改变箭头函数中this的指向
var id = 'Global';
let fun1 = () => {
console.log(this.id)
};
fun1(); // 'Global'
fun1.call({id: 'Obj'}); // 'Global'
fun1.apply({id: 'Obj'}); // 'Global'
fun1.bind({id: 'Obj'})(); // 'Global'
5. Proxy 实现简单的响应式代理
vue3 就是用Proxy
代替原本的Object.defineProperty
来实现数据响应式(但比我们自己实现的要复杂很多).
这一节将放下,在vue3设计与实践中,我们再来实现.
6. map和weakMap的区别
map类似于对象,但任何数据都可以作用键.
weakMap只接受对象作为键名
WeakMap的设计目的在于,有时想在某个对象上面存放一些数据,但是这会形成对于这个对象的引用。一旦不再需要这两个对象,就必须手动删除这个引用,否则垃圾回收机制就不会释放对象占用的内存。
而WeakMap的键名所引用的对象都是弱引用,即垃圾回收机制不将该引用考虑在内。因此,只要所引用的对象的其他引用都被清除,垃圾回收机制就会释放该对象所占用的内存。也就是说,一旦不再需要,WeakMap 里面的键名对象和所对应的键值对会自动消失,不用手动删除引用。
7. JavaScript为什么要进行变量提升,导致了什么问题
变量提升的表现是,无论在函数中何处位置声明的变量,好像都被提升到了函数的首部,可以在变量声明前访问到而不会报错。
造成变量声明提升的本质原因是 js 引擎在代码执行前有一个解析的过程,创建了执行上下文,初始化了一些代码执行时需要用到的对象。当访问一个变量时,会到当前执行上下文中的作用域链中去查找,而作用域链的首端指向的是当前执行上下文的变量对象,这个变量对象是执行上下文的一个属性,它包含了函数的形参、所有的函数和变量声明,这个对象的是在代码解析的时候创建的。
首先要知道,JS在拿到一个变量或者一个函数的时候,会有两步操作,即解析和执行。
- 在解析阶段
JS会检查语法,并对函数进行预编译。解析的时候会先创建一个全局执行上下文环境,先把代码中即将执行的变量、函数声明都拿出来,变量先赋值为undefined,函数先声明好可使用。在一个函数执行之前,也会创建一个函数执行上下文环境,跟全局执行上下文类似,不过函数执行上下文会多出this、arguments和函数的参数。
- 全局上下文:变量定义,函数声明
-
函数上下文:变量定义,函数声明,this,arguments
-
在执行阶段,就是按照代码的顺序依次执行。
那为什么会进行变量提升呢?主要有以下两个原因:
- 提高性能
- 容错性更好
(1)提高性能 在JS代码执行之前,会进行语法检查和预编译,并且这一操作只进行一次。这么做就是为了提高性能,如果没有这一步,那么每次执行代码前都必须重新解析一遍该变量(函数),而这是没有必要的,因为变量(函数)的代码并不会改变,解析一遍就够了。
在解析的过程中,还会为函数生成预编译代码。在预编译时,会统计声明了哪些变量、创建了哪些函数,并对函数的代码进行压缩,去除注释、不必要的空白等。这样做的好处就是每次执行函数时都可以直接为该函数分配栈空间(不需要再解析一遍去获取代码中声明了哪些变量,创建了哪些函数),并且因为代码压缩的原因,代码执行也更快了。
(2)容错性更好
变量提升可以在一定程度上提高JS的容错性,看下面的代码:
a = 1;var a;console.log(a);
如果没有变量提升,这两行代码就会报错,但是因为有了变量提升,这段代码就可以正常执行。
虽然,在可以开发过程中,可以完全避免这样写,但是有时代码很复杂的时候。可能因为疏忽而先使用后定义了,这样也不会影响正常使用。由于变量提升的存在,而会正常运行。
总结:
- 解析和预编译过程中的声明提升可以提高性能,让函数可以在执行时预先为变量分配栈空间
- 声明提升还可以提高JS代码的容错性,使一些不规范的代码也可以正常执行
变量提升虽然有一些优点,但是他也会造成一定的问题,在ES6中提出了let、const来定义变量,它们就没有变量提升的机制。下面看一下变量提升可能会导致的问题:
var tmp = new Date();
function fn(){
console.log(tmp);
if(false){
var tmp = 'hello world';
}
}
fn(); // undefined
在这个函数中,原本是要打印出外层的tmp变量,但是因为变量提升的问题,内层定义的tmp被提到函数内部的最顶部,相当于覆盖了外层的tmp,所以打印结果为undefined。
var tmp = 'hello world';
for (var i = 0; i < tmp.length; i++) {
console.log(tmp[i]);
}
console.log(i); // 11
由于遍历时定义的i会变量提升成为一个全局变量,在函数结束之后不会被销毁,所以打印出来11。
8. forEach和map方法的区别
- forEach()会针对每一个元素执行提供的函数,是对数组的直接操作,该方法没有返回值.
- map()方法则是创建一个新的数组并拷贝元数组的值,再对每一个元素执行提供的函数,最后再将新数组返回.
9. 作用域和作用域链
作用域
- 局部作用域
- 函数作用域
- 块作用域
- 全局作用域
作用域链
本质上是底层的变量查找机制,从当前作用域逐级往上查找,最后找到全局作用域.
10.垃圾回收机制(Garbage Collection GC)
JS的内存分配是自动完成的.内存不使用时会被垃圾回收器自动回收
JS分配的内存,一般有如下声明周期:
- 内存分配:分配变量,对象,函数时.
- 内存使用:读写变量,对象,使用函数时
- 内存回收,使用完毕,自动回收
全局变量一般不回收(关闭程序回收)
一般情况下,局部变量在使用完毕后也会回收.
内存泄漏:内存因为某种原因无法释放或者忘记释放,叫做内存泄漏.
浏览器的两种垃圾回收算法:引用计数法 和 标记清除法
1. 引用计数法
定义内存为”不再使用”,就是看一个对象是否有指向它的引用,没有引用则回收对象
算法:
- 跟踪记录被引用的次数
- 如果被引用一次,则计数次数+1,多次引用则累加++
- 减少一个引用减1 –
- 引用次数是0,则释放内存
[HTML_REMOVED]
通过循环引用(也叫嵌套引用),使得被引用的次数始终是1.
2. 标记清除法
标记清除法是计数法的改进,将定义”不再使用的对象” 定义为”无法到达的对象”
- 凡是从全局对象(也叫根部)出发,定时扫描内存中的对象,如果能从根部到达的对象,则说明还需要使用.
- 那些无法到达的对象会被标记,稍后进行回收
[HTML_REMOVED]
10. 闭包
概念: 一个函数对周围状态的引用捆绑到一起,内层函数中访问到外层函数的作用域.
闭包 = 内层函数 + 外层函数的变量
简单例子
function outer() {
const a = 1;
function f() {
console.log(a)
}
f();
}
outer();
[HTML_REMOVED]
1. 闭包的作用
封闭数据,提供操作,外部可以访问内部的变量.
function outer() {
let a = 10;
function fn() {
log(a);
}
return fn;
}
const fun = outer();
//个人对闭包的理解其实就像是Java 的 getter,setter函数一样.
//在不允许直接操作变量的情况下,提供一个接口对数据进行操作
闭包可能会引起的问题,内存泄漏.
11.深入对象
1.原型
个人理解是和java的静态方法差不多. 当一个类型的多个对象几乎都需要这个函数,而每次创建一个新对象,我们都需要在堆中为这个功能相同的函数再次创建,假设每次都console.log(a),那十个对象就要创建十次.
那如果放置原型,我们每次使用的就都是那个函数.省下了大量的空间.
原型是什么? 一个对象.
原型的作用是什么? 共享方法.将不变的方法,直接定义到prototype上.
构造函数和原型对象中的this都指向实例化的对象
2.对象原型
对象都有一个属性__proto__
指向构造函数的prototype对象,之所以对象可以使用构造函数的prototype原型对象的属性和方法. 就是因为对象原型的存在. [[prototype]] 和 __proto__
意义相同.
[[prototype]]
对象原型里面也有构造函数,指向创建该实例的构造函数
function Obj() {
//函数
}
let var = new Obj();
console.log(var.__proto__ === Obj.prototype) //true
3. 原型链
基于原型对象的继承,使得不同构造函数的原型对象关联在一起,并且这种关联的关系是一种链状结构,我们将原型对象的链状结构称为原型链.
12. 深浅拷贝
开发中我们会经常需要赋值一个对象,直接赋值(=) 存在问题.
const obj = {
name: "zhangsan",
age: 18
};
const o = obj;
log(o);
o.age = 20;
log(o.age); //20
log(obj.age); //20
在之前,我们了解到了对象能存储的引用. obj指向了的那块,存储的是一个地址,而o因为是直接赋值,所以两个指针(变量)的值是相同的.
那么,这该地址存储的是堆中的一块数据,而因为 obj 和 o 存储同一个地址,就会共用一块数据.
[HTML_REMOVED]
1.1 浅拷贝
首先深浅拷贝只针对引用类型
浅拷贝: 拷贝的是地址.
常见方法::
- 拷贝对象: Object.assign( ) / 展开运算符{ …obj } 拷贝对象
- 拷贝数组: Array.prototype.concat( ) 或者是 [ …arr ]
1.2 深拷贝
深拷贝: 拷贝的是对象(堆中的数据),而不再是地址
实现方式:
- 通过递归实现深拷贝
- lodash/cloneDeep
- 通过JSON.stringfy( ) 实现深拷贝
function deepCopy(newObj,oldObj) {
//遍历对象
for(let key in oldObj) {
//判断是否是数组
if(oldObj[k] instanceof Array) {
newObj[k] = [];
deepCopy(newObj[k],oldObj[k]);
} else if (oldObj[k] instanceof Object) {
newObj[k] = { };
deepCopy(newObj[k],oldObj[k]);
} else {
// k 属性名, oldObj[k] 属性值
newObj[k] = oldObj[k];
}
}
}
//js库lodash里面的cloneDeep内部实现深拷贝
const obj = {
name: "张三",
age: 19,
hobby: ["唱","跳","Rap"]
};
//语法: _.cloneDeep(obj)
const o = _.cloneDeep(Obj);
log(o);
o.hobby[2] = "篮球";
log(obj);
log(o);
const obj = {
name: "张三",
age: 19,
hobby: ["唱","跳","Rap"]
};
const o = JSON.parse(JSON.stringfy(Obj));
13. 普通函数和箭头函数的this
箭头函数本身是没有this,它的this 是根据上下文(去作用域链上的逐级去查找this).
coust user = {
name: "小明",
walk: ()=>{
log(this); //会一路查找到最大的Window对象
}
}
1.1call方法改变this指向
- call( ) - 了解
语法:
fun.call( thisArg,arg1,arg2, …)
thisArg: 在fun函数运行时指定的this值
argx : 参数
返回值就是该函数的返回值,因为它是调用函数
//1. 调用函数
//2. 改变this指向
const obj = {
age: 18
}
function fn() {
log(this) //正常情况下 window
}
fn.call(obj) //log打印: obj
1.2 apply方法改变this指向
语法: fun.apply(thisArg,[ argsArray ] )
const obj = {
age: 18
}
function fn() {
log(this) //正常情况下 window
}
fn.apply(obj) //log打印: obj
1.3 bind改变this指向
bind()方法不执行函数,但能改变函数内部this指向
fun.bind(thisArg,arg1,arg2,…)
返回由指定的this值,和初始化参数改造的原函数拷贝(新函数)
function sayHi( ) {
log(this)
}
let user = {
name: "小明",
age: 19
}
let sayHello = sayHi.bind(user);
//调用使用bind创建的新函数
sayHello()
14 防抖
防抖: 单位事件内,频繁触发事件,只执行最后一次
[HTML_REMOVED]
使用场景: 手机号,身份证,邮箱的验证. 用户框的搜索请求
防抖有lodash库提供的 _.debouce(fn,timeout)
手写防抖函数
/**
核心思路:
防抖的核心就是利用定时器(setTimeOut)来实现
1. 声明一个定时器变量
2. 当鼠标每次滑动都先判断是否有定时器,如果有定时器就先清除以前的定时器
**/
function debouce(fn,t) {
let timer = null;
return function() {
if(timer) clearTimeout(timer);
timer = setTimeout(()=>{
fn();
},t);
}
}
15.节流
节流: 单位时间内,频繁触发事件,只执行一次
\个人学习记录和总结\images\节流.png)
举例子:
王者荣耀的技能冷却. PUBG的狙击枪,换弹时无法射击.
使用场景: 鼠标移动,页面尺寸缩放,滚动条滚动.
实现方式:
_.throttle(fn,[wait = 0],[options]) //在wait秒内,最多只执行一次
1. 定义一个定时器变量
2. 每次鼠标滑动时,判断是否有定时器,有则不开新的定时器
3. 没有定时器则开启定时器,调用执行,定时器清空
function throttle(fn,t) {
let timer = null;
return function() {
if(!timer) {
timer = setTimeout(()=>{
fn();
timer = null; //不使用clearTimeout,原因是在setTimeout是无法执行清除的.
},t)
}
}
}
节流样例:记录上一次视频的播放时间
两个事件:
- ontimeupdate 事件在视频当前的播放位置发生改变时触发(也就是进度条的那个小方块移动时,如果不节流,9秒可能会触发40次
- 我们的目标则是让它变成一秒钟触发一次
- onLoadedData 事件在当前帧的数据加载完成且还没有足够的数据播放视频/音频的下一帧时触发
const video = document.getById("video");
video.ontimeupdate = function() {
log(11)
}
ontimeupdate 事件触发时,每个一秒钟将当前进度条的时间存储到本地存储
下次打开页面时,onLoadedData 事件触发,就从本地存储取取出时间,让视频从取出的时间开始播放.没有就默认为0
获取进度条当前的时间 video.currentTime
//方法一,调用库
video.ontimeupdate = _.throttle(()=>{
//节流,每秒记录时间
localStorage.setItem("currentTime",video.currentTime);
},1000)
video.onLoadedData = ()=>{
//当打开视频时,获取进度条时间
video.currentTime = localStorage.getItem("currentTime") || 0;
}