首页
统计
赞助
留言
关于
更多
友链
壁纸
直播
Search
1
Joe — 一款个人类型Typecho主题
31,846 阅读
2
Joe开源目录程序系统
11,762 阅读
3
Typecho自定义后台编辑器功能
8,179 阅读
4
Joe主题实现自动更新
7,460 阅读
5
Joe主题自定义搭配色教程
4,366 阅读
WEB前端
CSS
React
Vue 2.0
Vue 3.0
JavaScript
TypeScript
Typecho
小程序
苹果CMS
其他
生活
登录
Search
https://78.al
累计撰写
76
篇文章
累计收到
3,710
条评论
首页
栏目
WEB前端
CSS
React
Vue 2.0
Vue 3.0
JavaScript
TypeScript
Typecho
小程序
苹果CMS
其他
生活
页面
统计
赞助
留言
关于
友链
壁纸
直播
搜索到
76
篇与
HaoOuBa
的结果
2021-06-23
原型继承和 Class 继承
⾸先先来讲下 class ,其实在 JS 中并不存在类, class 只是语法糖,本质还是函数class Person {} Person instanceof Function // true组合继承function Parent(value) { this.val = value } Parent.prototype.getValue = function() { console.log(this.val) } function Child(value) { Parent.call(this, value) } Child.prototype = new Parent() const child = new Child(1) child.getValue() // 1 child instanceof Parent // true{callout color="#6db9f3"}以上继承的⽅式核⼼是在⼦类的构造函数中通过 Parent.call(this) 继承⽗类的属性,然后改变⼦类的原型为 new Parent() 来继承⽗类的函数。这种继承⽅式优点在于构造函数可以传参,不会与⽗类引⽤属性共享,可以复⽤⽗类的函数,但是也存在⼀个缺点就是在继承⽗类函数的时候调⽤了⽗类构造函数,导致⼦类的原型上多了不需要的⽗类属性,存在内存上的浪费{/callout}寄⽣组合继承这种继承⽅式对组合继承进⾏了优化,组合继承缺点在于继承⽗类函数时调⽤了构造函数,我们只需要优化掉这点就⾏了function Parent(value) { this.val = value } Parent.prototype.getValue = function() { console.log(this.val) } function Child(value) { Parent.call(this, value) } Child.prototype = Object.create(Parent.prototype, { constructor: { value: Child, enumerable: false, writable: true, configurable: true } }) const child = new Child(1) child.getValue() // 1 child instanceof Parent // trueclass继承class Parent { constructor(value) { this.val = value } getValue() { console.log(this.val) } } class Child extends Parent { constructor(value) { super(value) this.val = value } } let child = new Child(1) child.getValue() // 1 child instanceof Parent // true
2021年06月23日
424 阅读
3 评论
16 点赞
2021-06-22
饿了么优惠卷每天免费领3~20元红包
{message type="info" content="对于学生党,上班族来说,点外卖是每日必做的事情(博主也天天吃外卖!),对的,这里博主给大家分享 每天领取 饿了么优惠卷 美团优惠券 每单平均优惠1.5元 领取的饿了么红包 美团外卖红包 还可以与部分红包 叠加使用!!!"/}不要忘了 每天都可以领 每天点外卖省钱{callout color="#f0ad4e"}微信扫码领红包可以中随机金额外卖券经常点外卖的速度去有效期是24小时 请在当天使用第二天可以继续领取使用建议用不经常使用的微信号领取,中大包的概率更大{/callout}领取教程 ↓↓↓1、打开微信扫码领饿了外卖券->我老用户亲测中10元红包2、配合店铺满减->2块钱吃到了汉堡+鸡肉卷+小鸡腿+可乐3、外卖红包有效时间只有四个小时!不用掉就过期了!!PS:PS:我一个号经常用饿了么的号领了20-7,另一个不常用的老用户领了20-15,刚需吃外卖的话还不错的!打开微信扫描二维码领取 ↓ ↓ ↓{tabs}{tabs-pane label="饿了么红包"}{/tabs-pane}{tabs-pane label="美团红包"}{/tabs-pane}{/tabs}
2021年06月22日
662 阅读
5 评论
10 点赞
2021-06-18
前端面试题二
类数组的length{callout color="#8cd0f3"} var obj = { "2" : 3, "3" : 4, "length" : 2, "splice" : Array.prototype.splice, "push" : Array.prototype.push } obj.push(1) obj.push(2) console.log(obj) // { 2: 1, 3: 2, length: 4 }这题主要是将对象转换成了类数组,push方法其实就是在数组length的地方,添加一个元素 var obj = { "2" : 3, "3" : 4, "length" : 2, // 这个splice方法,我在jquery源码里也看到过这样写,贼牛逼 // 加上splice 或 push 后,直接将obj改成了Object创建的伪数组形式 "splice" : Array.prototype.splice, "push" : Array.prototype.push } obj[obj.length++] = 1 obj[obj.length++] = 2 // 这里因为改成了类数组,但是索引0 和 1的位置没有值,所以就变成了empty * 2,代表有2个空元素在这儿占位 console.log(obj) // Object(4) [empty × 2, 1, 2, splice: ƒ, push: ƒ]{/callout}操作符优先级题目{callout color="#8cd0f3"}let a = {n : 1}; let b = a; a.x = a = {n: 2}; console.log(a.x) // undefined console.log(b.x) // { n: 2}解析,上面的题目可以改成下面的let a = {n : 1}; let b = a; // 这一步将a.x 赋值成 {n: 2} // 此时 a = {n: 1, x: {n: 2}} b = {n: 1, x: {n: 2}} a.x = {n: 2} // 接着又将a初始化成 {n: 2} // 此时 a = {n: 2} , 地址会发生变化,因此a !== b。而 b 并仍然是上一次的值 b = {n: 1, x: {n: 2}} a = {n: 2} console.log(a.x) // undefined console.log(b.x) // {n: 2}{/callout}作用域相关{callout color="#8cd0f3"}var a = 0, b = 0; function A(a) { A = function (b) { console.log(a + b++) } console.log(a++) } A(1) // 1 A(2) // 4这题还好,明白变量作用域查找,就会了,上面的解析// 首先看A(1)的执行顺序,A(2) 需要放在后面看 var a = 0 var b = 0; function A(a) { // 这里我的理解是 // 这里只是把A赋值了,开辟了新的内存地址,他依旧是个函数,并且没有执行,不会影响上一次A(1)的函数 // 因此在执行A(1) 的时候,这步可以忽略 A = function (b) { console.log(a + b++) } // 如果在这里加上打印 a // 根据作用域查找规则,它会先在当前作用域查找,如果没有,就去上一级作用域查找 // 这里查找到了函数参数的a,并且值是传入进来的 1,因此下面的那句里的 a 的值就是1 console.log(a) // a++ 与 ++a 的区别,就非常简单了,一个后加在执行,一个先加在执行,这里是a++,所以肯定会打印1 console.log(a++) } A(1)接着看A(2)的执行顺序// 在A(1) 执行完后,a的值会自增1,变成了2,上面的代码就可以改成下面的 function A(b) { // 这里的 a 就是上次执行后,并自增了1的a // b++ 和上面的a++一样,先运算后执行 // 这里其实就是 console.log(2 + 2) console.log(a + b++) // 4 } A(2){/callout}非匿名自执行函数,函数名只读{callout color="#8cd0f3"}var b = 10; (function b(){ b = 20 console.log(b) // Function b })()解析:首先排除外面的b,因为作用域查找根本没有它什么事自执行函数内的b,根据作用域查找规则,这个b就是这个函数 function b接着将看似是将函数b改成了20,最后打印20但其实是:在自执行函数里面,如果这个自执行函数写了名字,例如b再去修改这个匿名函数,修改是没有反应的,即使没有报错,但是修改也不成功所以会打印这个函数b{/callout}稀疏数组{callout color="#8cd0f3"}var ary = [0,1,2]; ary[10] = 10; var r = ary.filter(function(x) { return x === undefined;}); console.log(r) // []0 in ary; => true3 in ary; => false10 in ary; => true也就是说 从 3 - 9 都是没有初始化的'坑'这些索引并不存在与数组中. 在 array 的函数调用的时候是会跳过这些'坑'的{/callout}包装类型与简单类型{callout color="#8cd0f3"}function showCase(value) { switch (value) { case 'A': console.log('Case A'); break; case 'B': console.log('Case B'); break; case undefined: console.log('undefined'); break; default: console.log('Do not know!'); } } showCase(new String('A')); // Do not know!这题只要知道包装类型是不等于非包装类型的就行了 new String("A") !== "A"而 String("A") === "A"{/callout}小学生题目{callout color="#8cd0f3"}1 + - + + + - + 1 = ?结果:2+- 是一元加和减操作符号,就是数学里的正负号。负负得正{/callout}
2021年06月18日
479 阅读
4 评论
9 点赞
2021-06-16
DOM同级传递事件
大家到现在所了解到的事件,基本都离不开浏览器的行为。比如点击鼠标、按下键盘等等,这些都可以被浏览器感知到,进而帮助我们转换成一个“信号”触发对应处理函数。但是还有一些行为,是浏览器感知不到的。比如说看这样一段 html<div class="a"></div> <div class="b"></div> <div class="c"></div>如果我们仅仅想监听 a 这个元素上的点击行为,我们可以用 addEventListener 来安装监听函数var a = document.getElementById('a') document.addEventListener('click',function(){ console.log('a') })但是,如果现在想实现这样一种效果在点击A之后,B 和 C 都能感知到 A 被点击了,并且做出相应的行为——就像这个点击事件是点在 B 和 C 上一样。我们知道,借助时间捕获和冒泡的特性,我们是可以实现父子元素之间的行为联动的。但是此处,A、B、C三者位于同一层级,他们怎么相互感知对方身上发生了什么事情呢?“A被点击了”这件事情,可以作为一个事件来派发出去,由 B 和 C 来监听这个事件,并执行各自身上安装的对应的处理函数。在这个思路里,“A被点击了”这个动作挺特别,特别就特别在浏览器不认识它。因为浏览器不认识它,所以浏览器不肯”帮忙“,不会帮咱去感知和派发这个动作。不过没关系,感知和派发,咱都可以自己来实现首先大家需要了解的是,自定义事件的创建。比如说咱们要创建一个本来不存在的"clickA"事件,来表示 A 被点击了,可以这么写<div class="a"></div> <div class="b"></div> <div class="c"></div> <script> /* 获取元素 */ const a = document.querySelector(".a") const b = document.querySelector(".b") const c = document.querySelector(".c") /* 创建一个自定义事件 */ const aEvent = new Event("sb250") a.addEventListener("click", function (e) { console.log('a'); /* 派发给b和c */ b.dispatchEvent(aEvent) c.dispatchEvent(aEvent) }) /* b监听 */ b.addEventListener("sb250", function (e) { console.log('b') }) c.addEventListener("sb250", function (e) { console.log('c') }) </script>可以看到,浏览器控制台能正确的打印 a b c,因此实现了对a点击事件的监听
2021年06月16日
251 阅读
1 评论
5 点赞
2021-06-15
Vue面试题
MVC 和 MVVM 区别{callout color="#68bbdf"}MVC controller 控制层将数据层 model 层 的数据处理后显示在视图层 view层,同样视图层 view层 接收用户的指令也可以通过控制层 controller,作用到数据层 modelMVVM 隐藏了 controller 控制层,直接操控 View 视图层和 Model 数据层。MVC 的视图层和数据层交互需要通过控制层 controller 属于单向链接。MVVM 隐藏了控制层 controller,让视图层和数据层可以直接交互 属于双向连接{/callout}Vue的事件绑定原理{callout color="#68bbdf"}Vue中通过v-on或其语法糖@指令来给元素绑定事件并且提供了事件修饰符,基本流程是进行模板编译生成AST,生成render函数后并执行得到VNode,VNode生成真实DOM节点或者组件时候使用addEventListener方法进行事件绑定{/callout}为什么 data 是一个函数{callout color="#68bbdf"}组件中的 data 写成一个函数,数据以函数返回值形式定义,这样每复用一次组件,就会返回一份新的 data,类似于给每个组件实例创建一个私有的数据空间,让各个组件实例维护各自的数据,防止变量污染{/callout}Vue 组件间通信{callout color="#68bbdf"}(1)props / $emit 适用 父子组件通信(2)$parent / $children 适用 父子组件通信(3)EventBus ($emit / $on) 适用于 父子、隔代、兄弟组件通信(4)$attrs/$listeners 适用于 隔代组件通信(5)provide / inject 适用于 隔代组件通信(6)Vuex 适用于 父子、隔代、兄弟组件通信(7)通过$refs获取组件实例{/callout}父组件如何监听到子组件的生命周期{callout color="#68bbdf"}(1)通过 $emit 触发父组件的事件// Parent.vue <Child @mounted="doSomething"/> <script> // Child.vue mounted() { this.$emit("mounted"); } </script>(2)通过hook来监听// Parent.vue <Child @hook:mounted="doSomething" ></Child>{/callout}vue-router 路由模式有几种{callout color="#68bbdf"}hash模式使用 URL hash 值来作路由。history依赖 HTML5 History APIabstract支持所有 JavaScript 运行环境,如 Node.js 服务器端如果发现没有浏览器的 API,路由会自动强制进入这个模式{/callout}Vue 数据双向绑定{callout color="#68bbdf"}实现一个监听器 Observer:对数据对象进行遍历,包括子属性对象的属性利用 Object.defineProperty() 对属性都加上 setter 和 getter这样的话,给这个对象的某个值赋值,就会触发 setter,那么就能监听到了数据变化实现一个解析器 Compile:解析 Vue 模板指令,将模板中的变量都替换成数据,然后初始化渲染页面视图并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者一旦数据有变动,收到通知,调用更新函数进行数据更新实现一个订阅者 Watcher:Watcher 订阅者是 Observer 和 Compile 之间通信的桥梁主要的任务是订阅 Observer 中的属性值变化的消息当收到属性值变化的消息时,触发解析器 Compile 中对应的更新函数。实现一个订阅器 Dep:订阅器采用 发布-订阅 设计模式,用来收集订阅者 Watcher对监听器 Observer 和 订阅者 Watcher 进行统一管理。{/callout}v-if 与 v-for 为什么不建议一起使用{callout color="#68bbdf"}v-for 和 v-if 不要在同一个标签中使用,因为解析时先解析 v-for 再解析 v-if。如果遇到需要同时使用时可以考虑写成计算属性的方式{/callout}props自定义验证{callout color="#68bbdf"}props: { num: { default: 1, validator: function (value) { // 返回值为true则验证不通过,报错 return [1, 2, 3, 4, 5].indexOf(value) !== -1 } } }{/callout}watch的immediate属性{callout color="#68bbdf"}比如平时created时要请求一次数据,并且当搜索值改变,也要请求数据,我们会这么写created(){ this.getList() }, watch: { searchInputValue(){ this.getList() } }使用immediate完全可以这么写,当它为true时,会初始执行一次watch: { searchInputValue:{ handler: 'getList', immediate: true } }{/callout}computed实现传参{callout color="#68bbdf"}new Vue({ el: "#app", data() { return { params: { name: '杜恒', age: 18 } } }, computed: { total() { // 通过返回一个高阶函数 return (arg) => { return arg * 10 } } } }){/callout}vue的hook的使用{callout color="#68bbdf"}我们常用的使用定时器的方式export default{ data(){ timer:null }, mounted(){ this.timer = setInterval(()=>{ console.log('1'); },1000); } beforeDestory(){ clearInterval(this.timer); } }上面做法不好的地方在于:全局多定义一个timer变量,使用hook这么做:export default{ methods:{ fn(){ const timer = setInterval(()=>{ console.log('1'); },1000); this.$once('hook:beforeDestroy',()=>{ clearInterval(timer); }) } } }{/callout}Vue的el属性和$mount优先级{callout color="#68bbdf"}如以下的情况,el的优先级会大于$mountnew Vue({ router, store, el: '#app', render: h => h(App) }).$mount('#root'){/callout}Vue中的动态指令和参数{callout color="#68bbdf"}<template> <aButton @[someEvent]="handleSomeEvent()" :[someProps]="1000" />... </template> <script> ... data(){ return{ ... someEvent: someCondition ? "click" : "dbclick", someProps: someCondition ? "num" : "price" } }, methods: { handleSomeEvent(){ // handle some event } } </script>{/callout}相同的路由组件如何重新渲染{callout color="#68bbdf"}经常遇到的情况是,多个路由解析为同一个Vue组件。问题是,Vue出于性能原因,默认情况下共享组件将不会重新渲染,如果尝试在使用相同组件的路由之间进行切换,则不会发生任何变化const routes = [ { path: "/a", component: MyComponent }, { path: "/b", component: MyComponent }, ];如果想要重新渲染,就需要使用key<template> <router-view :key="$route.path"></router-view> </template>{/callout}
2021年06月15日
293 阅读
3 评论
1 点赞
2021-06-11
手撸call apply bind
如果自己去实现call apply bind,看上去挺复杂,写起来其实就几行代码因为call和apply一样,只是传参不一样,所以我就只写一个call实现call(其实只有2行代码)/* 随便定义一个对象,待会将函数内的this指向指向倒这个对象 */ const obj = { name: '我是需要被绑定改变this指向的对象' } /* 需要改变this指向的函数,没有使用call时,this指向window */ function fn(arg) { console.log('fn---------', this); console.log('fn---------', arg); } /* * * 重写call方法 * target 需要把this改变到哪个目标 * args 传递进来的参数 */ Function.prototype.call = function (target, ...args) { /* 这里的this就指向上面的fn函数 */ console.log(this); /* 随便定义一个变量,用于在目标对象里存fn函数,这里使用symbol更好 */ target['sb250'] = this /* 这样target目标上就有个sb250的属性,并且属性值就是fn函数 */ console.log(target); /* 调用这个sb250,并把参数传入进去就实现this改变了 */ target['sb250'](args) /* 防止给target上多出多余没用的参数,在将sb250删除掉 */ delete target['sb250'] } fn.call(obj, '我是傻逼')实现bind因为bind的调用方式,是返回一个新函数,在调用一次,例如:fn.bind(null)(options),所以需要用到高阶函数/* 随便定义一个对象,待会将函数内的this指向指向倒这个对象 */ const obj = { name: '我是需要被绑定改变this指向的对象' } /* 需要改变this指向的函数,没有使用call时,this指向window */ function fn(arg) { console.log('fn---------', this); console.log('fn---------', arg); } /* * * 重写call方法 * target 需要把this改变到哪个目标 * args 传递进来的参数 */ Function.prototype.bind = function (target) { target['sb250'] = this /* 这里使用高阶函数接收参数 */ return (...args) => { target['sb250'](args) delete target['sb250'] } } fn.bind(obj)('我是大傻逼!!!')
2021年06月11日
388 阅读
2 评论
7 点赞
2021-06-10
前端面试题一
暂时性死区{callout color="#ad7bea"}在代码块内,使用let、const命令声明变量之前,该变量都是不可用的。这在语法上,称为 “暂时性死区”{/callout}JS判断类型{callout color="#ad7bea"}typeof检测不出null 和 数组,结果都为object,所以typeof常用于检测基本类型instanceof不能检测出number、boolean、string、undefined、null、symbol类型所以instancof常用于检测复杂类型以及级成关系constructornull、undefined没有construstor方法,因此constructor不能判断undefined和nullObject.prototype.toString.call可以判断任意类型{/callout}普通函数和箭头函数的区别{callout color="#ad7bea"}普通函数可以通过bind、call、apply改变this指向可以使用new关键字箭头函数自身没有this指向,this继承自外层不能通过bind、call、apply改变this指向不能使用new关键字,因为箭头函数没有constructor没有arguments{/callout}栈和堆的区别{callout color="#ad7bea"}栈原始数据类型保存到栈内存中占据空间小、大小固定属于被频繁使用数据,所以放入栈中存储堆引用数据类型保存到堆内存中占据空间大、大小不固定引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体{/callout}undefined 和 null 区别{callout color="#ad7bea"}undefined定义变量,但是没有赋值undefined是从null派生出来的null表示一个空对象引用区别typeof undefined // undefinedtypeof null // nullundefined == null // trueundefined === null // false{/callout}eval()的作用{callout color="#ad7bea"}eval(string) 函数计算 JavaScript 字符串,并把它作为脚本代码来执行{/callout}造成内存泄露的操作{callout color="#ad7bea"}闭包意外的全局变量被遗忘的定时器或回调没有清理的dom元素(赋值一个dom给一个变量,又removeChild这个dom,变量依旧存在内存中)事件监听: 没有正确销毁 (低版本浏览器可能出现){/callout}垃圾回收机制方式及内存管理{callout color="#ad7bea"}在定义变量时就完成了内存分配。当不在使用变量了就会被回收,因为其开销比较大,垃圾收集器会定期找出那些不在继续使用的变量,然后释放内存垃圾回收机制标记清除法当变量进入环境时,将这个变量标记为'进入环境'。当标记离开环境时,标记为‘离开环境’。离开环境的变量会被回收引用技计数法跟踪记录每个值被引用的次数,如果没有被引用,就会回收内存管理分配内存 > 使用内存 > 回收内存{/callout}闭包{callout color="#ad7bea"}闭包就是能够读取其它函数内部变量的函数使用方法:在一个函数内部创建另一个函数最大用处有两个:读取其他函数的变量值,让这些变量始终保存在内存中缺点:会引起内存泄漏(引用无法被销毁,一直存在){/callout}new关键字执行的操作{callout color="#ad7bea"}创建一个新对象空对象的__proto__指向构造函数的原型对象构造函数this指向替换成新对象返回新对象{/callout}下面的代码输出的内容{callout color="#ad7bea"}function Fn() { return 1 } console.log(new Fn) // 没有参数时,括号可以省略 function Fn2() { return true } console.log(new Fn2)答案:Fn {}Fn2 {} 当一个构造函数不是返回对象时,再去new这个构造函数,会强行返回由这个构造函数创建的新对象{/callout}script延迟引入的方式{callout color="#ad7bea"}defer属性<script src="" defer></script>立即下载,但是会等到整个页面都解析完成之后再执行async属性<script src="" async></script>不让页面等待脚本下载和执行(异步下载),但是无法控制加载的顺序动态创建script标签使用定时器延迟将script标签放在页面的最后{/callout}同步任务与微任务及宏任务{callout color="#ad7bea"}同步任务按顺序执行微任务和宏任务皆为异步任务宏任务一般是:setTimeout,setInterval、setImmediate微任务一般是:promise执行顺序:同步任务 > 微任务 > 宏任务{/callout}for...in 和 object.keys的区别{callout color="#ad7bea"}Object.keys不会遍历继承的原型属性for...in 会遍历继承的原型属性{/callout}["1", "2", "3"].map(parseInt) 结果{callout color="#ad7bea"}结果:[1, NaN, NaN]原理:// map方法接收一个参数,并会传递三个参数:el, index, arr [].map((el, index, arr) => {}) // 因此标题中的表达式可以看成下方的 ["1", "2", "3"].map((el, index, arr) => { return parseInt(el, index) }) // parseInt("1", 0) 1 // parseInt("2", 1) NaN // parseInt("3", 2) NaN{/callout}创建对象的3种方式{callout color="#ad7bea"}字面量方式创建const obj = {}通过new关键字创建const obj = new Object()通过Object身上的方法创建Object.create(null) // create创建的对象原型对象上没有继承Object的原型方法{/callout}创建数组的方式{callout color="#ad7bea"}字面量方式创建const arr = []通过new关键字创建const arr = new Array(1, 2, 3)通过Array身上的方法创建const arr = Array.of(1, 2, 3){/callout}Number() 的存储空间{callout color="#ad7bea"}Number类型的最大值为Number.MAX_SAFE_INTEGER。即Math.pow(2, 53) - 1 // 9007199254740991超过了这个大小,就会截取造成精度丢失在项目中,后端有时候会返回一个id字段,这个id可能是数字,而这个数字就有可能超出js的最大值。例如:const id = 9007199254740992564 console.log(id) // 9007199254740993000 将超出精度值后面的数字全部转换成了0解决方案:服务端解决,不要返回超过精度大小的数字通过json-bigint库解决{/callout}0.1 + 0.2 === 0.3 ?{callout color="#ad7bea"}答案:false0.1和0.2转换成二进制后会无限循环0.1 -> 0.0001100110011001...(无限循环)0.2 -> 0.0011001100110011...(无限循环)由于尾数位数限制,需要将后面多余的位截掉,这样就会导致被截取后的精度丢失总结:精度丢失可能出现在进制转化和对阶运算过程中{/callout}symbol用在哪{callout color="#ad7bea"}我的想法:symbol可以用来部署接口,例如对象身上没有接口,可以通过symbol来实现接口之前写的文章:https://78.al/post/106.htmlsymbol可以防止命名冲突之前写的文章:https://78.al/post/152.html{/callout}typeof NaN ? NaN是否等于NaN,判断一个NaN是否等于NaN{callout color="#ad7bea"}NaN的意思是非数值,本质还是数字类型,因此 typeof NaN = 'number'并且NaN.constructor === Number据我所知NaN是不等于NaN的,原因不知道如果想强行判断NaN等于NaN的话,可以通过Object.is方法Object.is(NaN, NaN) // true{/callout}判断一个对象是不是空对象{callout color="#ad7bea"}这个问题在之前的面试问到我了,今天又刷到了通过JSON方法const obj = { name: '1' } console.log(JSON.stringify(obj) === '{}');通过Object.keys方法const obj = { name: '1' } console.log(Object.keys(obj).length === 0);通过Object.getOwnPropertyNames这个和上面的差不多const obj = { name: '1' } // getOwnPropertyNames 用于获取对象自身的属性 console.log(Object.getOwnPropertyNames(obj).length);{/callout}原型{callout color="#ad7bea"}{/callout}将伪数组转换成真实数组的方法{callout color="#ad7bea"}通过...展开运算符通过Array.from()方法通过Array.prototype.slice.apply(arguments){/callout}手撸一个扁平化数组的方法{callout color="#ad7bea"}const arr = [1, 2, [3, [4, [5]]]] function flat(arr) { let result = [] arr.forEach(item => { if (Array.isArray(item)) { result = [...result, ...flat(item)] } else { result = [...result, item] } }); return result } console.log(flat(arr));{/callout}获取页面上一共使用了多少种元素{callout color="#ad7bea"}console.log([...new Set([...document.querySelectorAll('*')].map(el => el.tagName))]);{/callout}手动实现 instanceof 方法{callout color="#ad7bea"}function myInstanceof(left, right) { /* 定义右侧的原型,例如 Object.prototype */ const rightPrototype = right.prototype /* 定义初始时左侧的实例的原型 */ let leftProto = left.__proto__ while (1) { /* 如果左侧的原型找到了顶层null,表示没有匹配到与右侧对应的原型 */ if (!leftProto) return false /* 如果左侧的原型等于了右侧构造函数的原型,则说明是右侧构造函数创建了左侧的实例 */ if (leftProto === rightPrototype) return true /* 将左侧的原型继续往上层原型赋值,最终会赋值成null */ leftProto = leftProto.__proto__ } } /* 判断obj是否是对象类型 */ console.log(myInstanceof({}, Object));{/callout}简单实现节流和防抖{callout color="#ad7bea"}(1)节流let _last = +new Date() document.querySelector("input").addEventListener('input', () => { const _now = +new Date() if (_now - _last > 100) console.log(1); _last = _now })(2)防抖let _time = null document.querySelector("input").addEventListener('input', () => { clearTimeout(_time) _time = setTimeout(() => { console.log(1); }, 300); }){/callout}从输⼊ url 到展示的过程{callout color="#ad7bea"}(1)输入URL后进行DNS解析 浏览器先检测浏览器缓存里有没有解析过这个域名的对应ip地址。 如果没有解析过,那么就去hosts里面找,如果hosts里面也没有,那么就去dns服务器找,例如dnspod.cn(2)TCP 三次握⼿ 客户端发送 syn (同步序列编号) 请求,进⼊ syn_send 状态,等待确认 服务端接收并确认 syn 包后发送 syn+ack 包,进⼊ syn_recv 状态 客户端接收 syn+ack 包后,发送 ack 包,双⽅进⼊ established 状态(3)发送请求,分析 url ,设置请求报⽂(头,主体)(4)服务器返回请求的⽂件 ( html )(5)浏览器渲染 HTML parser --> DOM Tree CSS parser --> Style Tree attachment --> Render Tree layout : 布局 GPU painting : 像素绘制⻚⾯{/callout}模块化{callout color="#ad7bea"}使⽤模块化可以给我们带来以下好处解决命名冲突提供复⽤性提⾼代码可维护性模块化有以下类型:iife:立即执行函数类型amd:requirejscmd:seajses:es6模块化umd:jquery源码开头就是umd规范system:Symtem.jscommonjs:nodejs{/callout}1 instanceof Number ?{callout color="#ad7bea"}1 instanceof Number // falseinstanceof的语法是:object instanceof constructor1是primitive类型,原始数据类型使用 instanceof 都会返回false而例如 1..toFixed(2) 可以使用的原因是因为这里会将1包装成Number类型{/callout}jQuery 链式调用怎么实现的 ?{callout color="#ad7bea"}这里使用原生js来模拟这样的效果let fun = { fun1: function() { console.log("fun1"); return this; }, fun2: function() { console.log("fun2"); return this; }, fun3: function() { console.log("fun3"); return this; } } fun.fun1().fun2().fun3();这样就可以连续的输出字符串的fun1、fun2、fun3 了,原因是在每个方法后面加了一个return this{/callout}Object.create 手动实现
2021年06月10日
289 阅读
0 评论
1 点赞
2021-06-08
React学习笔记六
React拓展 - setStatesetState更新状态的两种写法1、setState({}, [callback])export default class Test extends Component { state = {count: 0} add = () => { const { count } = this.state this.setState({ count: 8 }) console.log(this.state.count) // 0 因为setState是异步的,所以会先执行这个 // 如果想要在setState操作后,拿state里的值进行操作,需要使用第二个回调函数或async await实现 this.setState({ count: 8 }, () => { console.log(this.state.count) // 8 }) } }2、setState(updater, [callback])export default class Test extends Component { state = {count: 0} add = () => { // 通过函数式,可以获取到state和props this.setState((state, props) => { console.log(state, props) return { count: state.count + 1 } }, () => { console.log(this.state.count) // 1 }) } }React拓展 - lazyLoad路由懒加载import {Component, lazy, Suspense} from "react" // 懒加载路由模块 const Home = lazy(() => import("./Home/index")) const About = lazy(() => import("./About/index")) export default class Test extends Component { render() { return ( <div> {/* Suspense 用于网络不好的时候,显示一个loading加载 */} <Suspense fallback={<h1>Loading...</h1>}> <Route path="/home" component={Home}> <Route path="/about" component={About}> </Suspense> </div> ) } }React拓展 - State HookState Hook 为了让使用者在函数式组件里使用stateimport React from 'react'; export default function Test2() { // React.useState() 用于初始化一个值,返回一个数组 // 数组第一个值为初始化的值 // 第二值为一个函数,用于修改第一个值 const [count, setCount] = React.useState(0); const [name, setName] = React.useState('杜恒'); // 点击事件的回调 const changeCount = () => setCount(count + 1); const changeName = () => setName('帅气的杜恒'); return ( <div> <h2>当前值为:{count}</h2> <button onClick={changeCount}>+ 1</button> <hr /> <h2>当前名为:{name}</h2> <button onClick={changeName}>改名</button> </div> ); }React拓展 - Effect HookEffect Hook 为了让使用者在函数式组件里使用生命周期钩子import React from 'react'; export default function Test2() { const [count, setCount] = React.useState(0); // React.useEffect 接收两个参数 // 第一个参数为函数,这个函数可以用作2个生命周期 // componentDidMount 或 componentDidUpdate // 第二个参数为数组,这个数组用于监听state里的值 // 如果为空数组,则第一个参数当componentDidMount使用(只调用一次) // 如果第二个参数没写或数组有值,则第一个参数当componentDidUpdate使用 React.useEffect(() => { let timer = setInterval(() => { setCount(count => count + 1); }, 1000); // 当return函数出去时,这个return出去的函数就可以当componentWillUnmount使用 return () => { clearInterval(timer); }; }, []); return ( <div> <h2>当前值为:{count}</h2> </div> ); }React拓展 - Ref HookRef Hook 和类式组件里面的createRef一样import React from 'react'; export default function Test2() { const inputRef = React.useRef(); const show = () => console.log(inputRef); return ( <div> <input type="text" ref={inputRef} /> <button onClick={show}>点击提示</button> </div> ); }React拓展 - Fragment用于多个标签时包裹使用import React, {Fragment} from 'react'; export default function Test2() { return ( <Fragment> <input type="text" /> <input type="text" /> </Fragment> ); }上面的写法可以写成下面的,直接使用空标签import React, {Fragment} from 'react'; export default function Test2() { return ( <> <input type="text" /> <input type="text" /> </> ); }React拓展 - ContextContext 用于祖组件向后台组件通信import React, { Component } from 'react'; // 1. 创建TestContext容器 const TestContext = React.createContext(); export default class A extends Component { state = { name: '杜恒' }; render() { return ( <div style={{ background: 'orange', padding: 10 }}> 我是A组件 {/* 2. 通过 Provider 将所有的state传入到后代组件里 */} <TestContext.Provider value={this.state}> <B /> </TestContext.Provider> </div> ); } } class B extends Component { render() { return ( <div> 我是B组件 <C /> </div> ); } } // 第一种方式 class C extends Component { /* 3. 申明使用context */ static contextType = TestContext; render() { console.log(this.context); // {name: "杜恒"} return <div>我是C组件</div>; } } // 第二种方式 function C() { return ( <div> <TestContext.Consumer> { value => console.log(value) } </TestContext.Consumer> </div> ) }React拓展 - 组件优化组件有以下2个问题组件内执行了setState,即使不改变状态数据,也会触发render更新父组件render发生更新,会导致子组件的render也会触发由于render受shouldComponentUpdate的阀门控制,因此需要在shouldComponentUpdate钩子里进行操作import { Component } from 'react'; export default class A extends Component { state = { count: 0 }; /* 赋值一个空对象,并没有改变state的值,也会触发更新 */ add = () => this.setState({}); /* 通过控制shouldComponentUpdate的返回值,控制是否触发render */ shouldComponentUpdate(nextProps, nextState) { console.log(nextProps, nextState); return !this.state.count === nextState.count; } render() { console.log('render函数被触发了'); return <button onClick={this.add}>{this.state.count}</button>; } }{callout color="#ab72da"}在state里的值少的时候,可以这样写,但是当数据多的时候就不行了,因此另一种方案是将 Component 替换成 PureComponent。PureComponent 里已经默认实现了 shouldComponentUpdate 里的逻辑,上面的写法可以改成下面的写法{/callout}import { PureComponent } from 'react'; export default class A extends PureComponent { state = { count: 0 }; add = () => this.setState({}); render() { console.log('render函数被触发了'); return <button onClick={this.add}>{this.state.count}</button>; } }React拓展 - Render Propsimport { PureComponent } from 'react'; export default class A extends PureComponent { render() { return ( <div> <h3>我是A组件</h3> <B render={name => <C name={name} />}></B> </div> ); } } class B extends PureComponent { state = { name: '杜恒' }; render() { return ( <div> <h3>我是B组件</h3> {/* 类似插槽的功能 */} {this.props.render(this.state.name)} </div> ); } } class C extends PureComponent { render() { console.log(this.props); return <div>我是C组件</div>; } }错误边界错误边界用于当子组件出错时,控制错误范围在父组件,不会影响到其他的组件。错误边界只能在生产环境使用,开发环境会一闪而过import { PureComponent } from 'react'; export default class A extends PureComponent { /* 父组件定义一个状态,用于判断是否出错 */ state = {hasError: ''}; /* 当后代组件生命周期里有错误时,会触发该函数 */ static getDerivedStateFromError(error) { return { hasError: error }; } /* 这里可以抓捕到错误后,将错误发送给服务器,服务器在通知到钉钉等 */ componentDidCatch() { console.log('组件发生错误!') } render() { return ( <div> <h3>我是A组件</h3> {/* 动态渲染组件 */} {this.state.hasError ? <h2>子组件出错</h2> : <B />} </div> ); } } class B extends PureComponent { state = { name: '杜恒' }; render() { return ( <div> <h3>我是B组件</h3> {this.state.name.map(() => ( <span>666</span> ))} </div> ); } }
2021年06月08日
238 阅读
0 评论
2 点赞
2021-06-07
React学习笔记五
Redux什么是ReduxRedux是一个专门用于做状态管理的JS的库可以用在Vue、React、Angular中,但最常与React使用作用:集中式管理React应用中多个组件共享的状态安装redux 和 react-reduxnpm i redux -S npm i react-redux -SRedux使用1、src目录下创建redux文件夹,redux文件夹内创建reducers文件夹,用于管理reducers,例如新建一个count的reducer{callout color="#51d2ad"}reducer可以初始化状态,也可以操作状态{/callout}// 第一次prevState会默认为undefined,因此此时可以为其赋值,初始化一个状态值 // 这里的action就是第二步action传递过来的对象 export default function countReducer(prevState = 0, action) { switch (action.type) { case 'add': return prevState + action.data; default: return prevState; } }2、redux文件夹内创建actions文件夹,用于管理actions,例如新建一个count的action// action的作用是将此对象分发给store,接着reducer会进行加工处理 export const createAddAction = data => ({ type: 'add', data });3、redux文件夹内创建store文件// createStore 用于创建store // combineReducers 用于整合多个reducer import { createStore, combineReducers } from 'redux'; import countReducer from './reducers/count'; const allReducer = combineReducers({ count: countReducer }); export default createStore(allReducer);4、组件内获取store里的值和分发事件给reducerimport { Component } from 'react'; // 用于连接redux import { connect } from 'react-redux'; // 用于分发事件 import { createAddAction } from '../../redux/actions/count'; class Count extends Component { add = () => { this.props.add(1); }; render() { const { count } = this.props; return ( <div> <div>当前求和为:{count}</div> <button onClick={this.add}>+</button> </div> ); } } // state => ({ count: state.count }) state 就是redux里的内容,会将内容传递给Count组件 // { add: createAddAction } 会传递一个add事件给Count组件,Count组件内调用add方法会调用createAddAction,接着React-Redux会分发这个事件给Reducer export default connect(state => ({ count: state.count }), { add: createAddAction })(Count);5、入口文件内,添加Provider供全局公用storeimport store from './redux/store'; import { Provider } from 'react-redux'; ReactDOM.render( <Provider store={store}> <App /> </Provider>, document.querySelector('#root') );
2021年06月07日
145 阅读
0 评论
2 点赞
2021-06-04
React学习笔记四
React-Router-Dom首先需要安装路由的依赖库,注意名字为 react-router-domnpm i react-router-dom -S标签方式的实现路由控制1、首先 index.js 里面为 组件包裹一个 BrowserRouter 或 HashRouter,两者区别在于地址栏有没有 # 的链接,这样保证整个项目里的路由都是同一个路由器管理import { BrowserRouter } from 'react-router-dom'; import App from './App'; ReactDOM.render( <BrowserRouter> <App /> </BrowserRouter>, document.querySelector('#root') );2、App.jsx 里面通过标签实现路由控制{callout color="#4eda96"}在路由组件(例如About和Home组件)里面,会默认接收到4个props属性{/callout}import { Component } from 'react'; import { Link, Route } from 'react-router-dom'; import About from './views/About/About'; import Home from './views/Home/Home'; export default class App extends Component { render() { return ( <div> {/* 点击跳转到对应路由 */} <NavLink to="/about" activeClassName="current">About</NavLink> <NavLink to="/home" activeClassName="current">Home</NavLink> {/* 使用switth可以提高匹配效率 */} <Switch> {/* 根据不同的路由地址,显示对应的组件 */} <Route path="/about" component={About} /> <Route path="/home" component={Home} /> </Switch> </div> ); } }Tips:解决二级路由刷新页面资源丢失的3种办法public/index.html 里将 ./assets 类型的改为 /assetspublic/index.html 里将 ./ 改为 %PUBLIC_URL%public/index.html 里将 BrowserRouter 改为 HashRouter路由重定向import { Component } from 'react'; import { Link, Route, Redirect } from 'react-router-dom'; import About from './views/About/About'; import Home from './views/Home/Home'; export default class App extends Component { render() { return ( <div> <NavLink to="/about" activeClassName="current">About</NavLink> <NavLink to="/home" activeClassName="current">Home</NavLink> <Switch> <Route path="/about" component={About} /> <Route path="/home" component={Home} /> {/* 当所有路由都没有匹配上,会执行重定向 */} <Redirect to="/about"/> </Switch> </div> ); } }路由传参1、向路由组件传递params参数News组件import Detail from './Detail/Detail'; export default class News extends Component { render() { return ( <div> <Link to="/home/news/detail/001">新闻1</Link> <Link to="/home/news/detail/002">新闻2</Link> <Link to="/home/news/detail/003">新闻3</Link> {/* 通过冒号动态接收 */} <Route path="/home/news/detail/:id" component={Detail} /> </div> ); } }Detail组件export default class News extends Component { render() { {/* 此处会接收到传递的参数 */} console.log(this.props.match.params); return <div>{this.props.match.params.id}</div>; } }2、向路由组件传递search参数News组件export default class News extends Component { render() { return ( <div> <Link to="/home/news/detail/?id=001">新闻1</Link> <Link to="/home/news/detail/?id=002">新闻2</Link> <Link to="/home/news/detail/?id=003">新闻3</Link> <Route path="/home/news/detail" component={Detail} /> </div> ); } }Detail组件export default class News extends Component { render() { console.log(this.props.location.search); return <div>Test</div>; } }3、向路由组件传递state参数,当清除浏览器记录后,会丢失数据News组件export default class News extends Component { render() { return ( <div> <Link to={{ pathname: '/home/news/detail', state: { id: '001' } }}>新闻1</Link> <Link to={{ pathname: '/home/news/detail', state: { id: '002' } }}>新闻2</Link> <Link to={{ pathname: '/home/news/detail', state: { id: '003' } }}>新闻3</Link> <Route path="/home/news/detail" component={Detail} /> </div> ); } }Detail组件export default class News extends Component { render() { console.log(this.props.location.state); return <div>Test</div>; } }编程式路由1、压栈式跳转(有历史记录)export default class News extends Component { linkTo = id => { this.props.history.push({ pathname: '/home/news/detail', state: { id } }); }; render() { return ( <button onClick={() => this.linkTo('001')}>新闻1</button> ); } }2、非压栈式跳转(直接替换,无历史记录)export default class News extends Component { linkTo = id => { this.props.history.replace('/home/news/detail',{ id }); }; render() { return ( <button onClick={() => this.linkTo('001')}>新闻1</button> ); } }一般组件增加路由功能{callout color="#986de8"}默认情况下,一般组件是没有路由功能的,因此就需要借助 withRouter 来实现路由功能{/callout}import { withRouter } from 'react-router-dom'; class Test extends Component { go = () => { console.log(this.props) }; render() { return <button onClick={() => this.go}>新闻1</button> } } export default withRouter(Test)
2021年06月04日
162 阅读
0 评论
2 点赞
2021-06-03
VSCode插件备份
Atom One Dark ThemeVSCode的主题色ES7 React/Redux/React-Native/JS snippetsReact专用Live Server开启一个服务访问htmlminify压缩JS代码PHP IntelephensePHP专用Prettier Formatter格式化代码专用scss-to-css将scss转换成css,并自动增加浏览器兼容性写法VeturVue专用
2021年06月03日
454 阅读
3 评论
19 点赞
2021-06-01
React学习笔记三
React脚手架生成的index.js内容简介import React from 'react'; import ReactDOM from 'react-dom'; import App from './App'; import reportWebVitals from './reportWebVitals'; ReactDOM.render( // 开启严格模式,开启后,可以检测到app组件里的规范,例如使用字符串的 ref="" <React.StrictMode> <App /> </React.StrictMode>, document.getElementById('root') ); // 用于记录页面性能 reportWebVitals();子组件向父组件传值1、父组件export default class P extends Component { addTodo = name => { const { todos } = this.state; }; render() { return ( <div> <Header addTodo={this.addTodo}/> </div> ); } }2、子组件export default class Header extends Component { handleClick = () => { this.props.addTodo(88888) } render() { return ( <div onClick={this.handleClick}> Test </div> ); } }兄弟组件之间传值(消息订阅与发布){message type="info" content="消息订阅与发布,通常使用 PubSubJS 库进行实现"/}例如有两个兄弟组件 A 和 B。1、A组件需要接收B组件传递过来的数据,那么A组件需要先订阅一个消息export default class A extends Component { state = { items: [], isFirst: true, isLoading: false, error: '' }; componentDidMount() { // 订阅一个叫做 STATE 的消息 PubSub.subscribe('STATE', (msg, state) => { console.log(msg, state) if (msg === 'STATE') this.setState(state); }); } render() { return ( <div></div> ); } }2、B组件发布消息给A组件export default class Search extends Component { render() { return ( <div> <button onClick={this.searchUser}>搜索</button> </div> ); } /* 搜索用户 */ searchUser = () => { PubSub.publish('STATE', { isFirst: false, isLoading: true, error: '' }); }; }通过reduce实现数组的统计例如统计数组里面done为true的数量const arr = [ { name: 'a', done: true }, { name: 'b', done: true }, { name: 'c', done: false } ] const doneCount = arr.reduce((prev, current) => { return prev + (current.done ? 1 : 0) }, 0) console.log(doneCount) // 2React中代理服务器配置跨域的2种方式{callout color="#7f68f3"}配置跨域的原理:例如当前的项目地址为:http://localhost:3000,会开启一个微型的 http://localhost:3000 代理服务器,当发送请求后,会将请求发送给这个微型服务器,接着由微型服务器再发送给服务端,最后这个微型服务器将结果再转发给当前项目地址。这个微型服务器因为没有ajax引擎,所以不会有跨域阻断问题{/callout}1、通过修改package.json方式例如,需要请求到的服务器的域名及端口为 http://localhost:5000 在package.json文件中,添加以下的选项{ ..., "proxy": "http://localhost:5000", "proxy": "需要代理的服务器域名和端口号" }例如,当前启动项目的地址是 http://localhost:3000/接着将之前的请求地址改为本地的地址import { Component } from 'react'; import axios from 'axios'; export default class App extends Component { getData = async () => { try { // const res = await axios.get('http://localhost:5000/students'); 修改前 const res = await axios.get('http://localhost:3000/students'); // 修改后 const res = await axios.get('/students'); // 或者改成这种 console.log(res); } catch (e) { console.log(e); } }; render() { return <button onClick={this.getData}>click</button>; } }{callout color="#4da9ef"}Tips:代理服务器发送请求时,会优先public文件夹寻找,如果有对应的文件,则直接请求对应的文件,如果没有,才向服务端发送请求。例如请求 localhost:3000/index.html,并不会直接发送代理发送请求给 localhost:5000/index.html 而是直接请求了 public 文件夹里的 index.html 文件{/callout}2、通过 setupProxy 文件先在 src 下创建 setupProxy.js 的文件const proxy = require('http-proxy-middleware'); module.exports = function (app) { app.use( /* 遇见 /api1 前缀的请求,就会触发该配置 */ proxy('/api1', { /* 将请求转发给 http://localhost:5000 */ target: 'http://localhost:5000', /* * 控制服务器收到请求头的中 Host 的值 * 为 true 的时候,服务器端接收到的请求 Host 为 http://localhost:5000 * 为 false 的时候,服务器端接收到的请求 Host 为 http://localhost:3000 */ changeOrigin: true, /* * 重写请求路径 * 将 /api1 替换成空 */ pathRewrite: { '^/api1': '' } }) ); };发送请求import { Component } from 'react'; import axios from 'axios'; export default class App extends Component { getData = async () => { try { // 上面的代理后,会将此处的 api1 替换成空,实际请求的地址为 http://localhost:5000/students const res = await axios.get('/api1/students'); console.log(res); } catch (e) { console.log(e); } }; render() { return <button onClick={this.getData}>click</button>; } }React脚手架配置代理.md
2021年06月01日
226 阅读
0 评论
2 点赞
1
2
3
...
7