哪吒前端周刊 | 第001期
前言
哪吒前端周刊 | 第001期
你是不是还在computed中使用this?
在computed属性中通过this.xxx去拿data里面的数据,和methods里面的方法吧,或许还会通过this.route去获取路由里面的数据吧。其实,我们可以避免这些丑陋的this,它甚至会给我们带来看不见的性能问题。实现上,我们通过this能访问到的数据,在computed的第一个参数上都能结构出来。
如何避免v-if和v-for一起使用?
为什么要避免v-if和v-for在同一个元素上同时使用呢?因为在vue的源码中有一段代码时对指令的优先级的处理,这段代码是先处理v-for再处理v-if的。所以如果我们在同一层中一起使用两个指令,会出现一些不必要的性能问题,比如这个列表有一百条数据,再某种情况下,它们都不需要显示,当vue还是会循环这个100条数据显示,再去判断v-if,因此,我们应该避免这种情况的出现。
$attrs和$listeners
让你封装组件如鱼得水
包含了父作用域中不作为 prop 被识别 (且获取) 的特性绑定 (class
和 style
除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class
和 style
除外),并且可以通过 v-bind="$attrs"
传入内部组件——在创建高级别的组件时非常有用。
接收除了props声明外的所有绑定属性(class、style
除外)
通过 v-bind="$attrs"
, 可以将属性继续向下传递
包含了父作用域中的 (不含 .native
修饰器的) v-on
事件监听器。它可以通过 v-on="$listeners"
传入内部组件——在创建更高层次的组件时非常有用。
接收除了带有.native
事件修饰符的所有事件监听器
可以通过 v-on="$listeners"
,将事件监听器继续向下传递
浏览器渲染过程如下
解析HTML,生成DOM树,解析CSS,生成CSSOM树 将DOM树和CSSOM树结合,生成渲染树(RenderTree) Layout(回流):根据生成的渲染树,进行回流(Layout),得到节点的几何信息(位置,大小) Painting(重绘):根据渲染树以及回流得到的几何信息,得到节点的绝对像素 Display:将像素发送给GPU,展示在页面上。
为了构建渲染树,浏览器主要完成了以下工作:
从DOM树的根节点开始遍历每个可见节点。 对于每个可见的节点,找到CSSOM树中对应的规则,并应用它们。 根据每个可见节点以及其对应的样式,组合生成渲染树。
第一步中,既然说到了要遍历可见的节点,那么我们得先知道,什么节点是不可见的。不可见的节点包括:
一些不会渲染输出的节点,比如script、meta、link等。 一些通过css进行隐藏的节点。比如display:none。注意,利用visibility和opacity隐藏的节点,还是会显示在渲染树上的。只有display:none的节点才不会显示在渲染树上。
注意:渲染树只包含可见的节点
回流前面我们通过构造渲染树,我们将可见DOM节点以及它对应的样式结合起来,可是我们还需要计算它们在设备视口(viewport)内的确切位置和大小,这个计算的阶段就是回流。
[Vue事件总线:this.$bus.$emit与this.$bus.$on
]
1.创建Vue实例
复制//main.js
Vue.prototype.$bus = new Vue();
2.发射事件
复制//GoodsList
this.$bus.$emit("aaa")
3.监听事件
复制//home.vue
this.$bus.$on("aaa",()=>{
this.$refs.scroll.scroll.refresh()
})
4.示例:监听图片加载
复制//GoodsListItem.vue
"showImage"
alt=""
@load="imgLoad" />
imgLoad() {
if (this.$route.path.indexOf("/home") !== 1) {
this.$bus.$emit("homeImgLoad");
} else if (this.$route.path.indexOf("/detail") !== 1) {
this.$bus.$emit("detailImgLoad");
}
},
复制//home.vue
mounted() {
const refresh = debounce(this.$refs.scroll.refresh, 50);
this.$bus.$on("homeImgLoad", () => {
refresh();
});
},
复制//detail.vue
mounted() {
const refresh = debounce(this.$refs.scroll.refresh, 50);
this.$bus.$on("detailImgLoad", () => refresh());
},
pinyin-engine
这是一款简单高效的拼音匹配引擎,它能使用拼音够快速的检索列表中的数据。
使用索引以及缓存机制,从而在客户端实现毫秒级的数据检索 它的字典数据格式经过压缩处理,简体中文版本仅仅 17kb 大小(Gzip) 支持多音字、支持拼音首字母匹配 简体版本覆盖 6718 个汉字,繁体中文覆盖 20846 个汉字
https://www.npmjs.com/package/pinyin-engine
JavaScript Array map() 方法
数组中的每个元素乘于输入框指定的值,并返回新数组:
var numbers = [65, 44, 12, 4];
function multiplyArrayElement(num) {
return num * document.getElementById("multiplyWith").value;
}
function myFunction() {
document.getElementById("demo").innerHTML = numbers.map(multiplyArrayElement);
}
JS数组扁平化(flat)方法
Array.prototype.flat()
用于将嵌套的数组“拉平”,变成一维数组。该方法返回一个新数组,对原数据没有影响。
[1, 2, [3, 4]].flat()
// [1, 2, 3, 4]
flat()默认只会“拉平”一层,如果想要“拉平”多层的嵌套数组,可以将flat()方法的参数写成一个整数,表示想要拉平的层数,默认为1。
[1, 2, [3, [4, 5]]].flat()
// [1, 2, 3, [4, 5]]
[1, 2, [3, [4, 5]]].flat(2)
// [1, 2, 3, 4, 5]
上面代码中,flat()的参数为2,表示要拉平两层的嵌套数组。
如果不管有多少层嵌套,都要转成一维数组,可以用Infinity关键字作为参数。
[1, [2, [3]]].flat(Infinity)
// [1, 2, 3]
如果原数组有空位,flat()方法会跳过空位。
[1, 2, , 4, 5].flat()
// [1, 2, 4, 5]
flatMap()方法对原数组的每个成员执行一个函数,相当于执行Array.prototype.map(),然后对返回值组成的数组执行flat()方法。该方法返回一个新数组,不改变原数组。
// 相当于 [[2, 4], [3, 6], [4, 8]].flat()
[2, 3, 4].flatMap((x) => [x, x * 2])
// [2, 4, 3, 6, 4, 8]
flatMap()只能展开一层数组。
flatMap()
类似于map()
,但是它的callback返回的是扁平的一维数组(如果没有特别指定depth参数的话)。
const scattered = [ "my favorite", "hamburger", "is a", "chicken sandwich" ];
// map() 返回的是嵌套的数组results in nested arrays
const huh = scattered.map( chunk => chunk.split( " " ) );
console.log( huh ); // [ [ "my", "favorite" ], [ "hamburger" ], [ "is", "a" ], [ "chicken", "sandwich" ] ]
const better = scattered.flatMap( chunk => chunk.split( " " ) );
console.log( better ); // [ "my", "favorite", "hamburger", "is", "a", "chicken", "sandwich" ]
[for in 和 for of 的区别]
for...in
循环:只能获得对象的键名,不能获得键值
for...of
循环:允许遍历获得键值
var arr = ['red', 'green', 'blue']
for(let item in arr) {
console.log('for in item', item)
}
/*
for in item 0
for in item 1
for in item 2
*/
for(let item of arr) {
console.log('for of item', item)
}
/*
for of item red
for of item green
for of item blue
*/
forEach()
JS中find方法
用法
find() 方法返回通过测试(函数内判断)的数组的第一个元素的值。
如果没有符合条件的元素返回 undefined
find() 对于空数组,函数是不会执行的。
find() 并没有改变数组的原始值。
array.find(function(currentValue, index, arr),thisValue),其中currentValue为当前项,index为当前索引,arr为当前数组
[vue项目结合vuex、localstorage实现本地储存token]
首次登录时,后端服务器判断用户账号密码正确之后,根据用户id、用户名、定义好的秘钥、过期时间生成 token ,返回给前端; 前端拿到后端返回的 token ,存储在 localStroage 和 Vuex 里; 前端每次路由跳转,判断 localStroage 有无 token ,没有则跳转到登录页,有则请求获取用户信息,改变登录状态; 每次请求接口,在 Axios 请求头里携带 token; 后端接口判断请求头有无 token,没有或者 token 过期,返回401; 前端得到 401 状态码,重定向到登录页面。
在页面重新加载之间保持并补充Vuex状态。
vuex-persistedstate
Usage
import { createStore } from "vuex";
import createPersistedState from "vuex-persistedstate";
const store = createStore({
// ...
plugins: [createPersistedState()],
});
import Vue from 'vue'
import Vuex from 'vuex';
import createPersistedState from 'vuex-persistedstate';
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
count: 0
},
plugins: [createPersistedState()],
mutations: {
increment: state => state.count++,
decrement: state => state.count--
}
});
new Vue({
el: '#app',
computed: {
count() {
return store.state.count;
}
},
methods: {
increment() {
store.commit('increment');
},
decrement() {
store.commit('decrement');
}
}
});
import Vue from 'vue'
import Vuex from 'vuex';
import createPersistedState from 'vuex-persistedstate';
import Cookies from 'js-cookie';
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
count: 0
},
plugins: [createPersistedState({
storage: {
getItem: key => Cookies.get(key),
setItem: (key, value) => Cookies.set(key, value, { expires: 3, secure: true }),
removeItem: key => Cookies.remove(key)
}
})],
mutations: {
increment: state => state.count++,
decrement: state => state.count--
}
});
new Vue({
el: '#app',
computed: {
count() {
return store.state.count;
}
},
methods: {
increment() {
store.commit('increment');
},
decrement() {
store.commit('decrement');
}
}
});
[JavaScript 多个空格替换成1个空格]
// 把多个空格替换成1个空格
let value = '1 张三';
value = value.replace(/ +/g, ' ');
console.log(value);
// 输出:1 张三
js-cookie中文文档
set方法支持的属性
expires
定义有效期。如果传入Number,那么单位为天,你也可以传入一个Date对象,表示有效期至Date指定时间。默认情况下cookie有效期截止至用户退出浏览器。path
string,表示此cookie对哪个地址可见。默认为”/”。domain
string,表示此cookie对哪个域名可见。设置后cookie会对所有子域名可见。默认为对创建此cookie的域名和子域名可见。secure
true或false,表示cookie传输是否仅支持https。默认为不要求协议必须为https。
异步队列
var funcs = [func1, func2, func3];
var funcPromise = funcs.map(function(func, i) {
return new Promise(function(resolve) {
func();
console.log('func'+(i+1)+' well done!');
resolve(); //如果 func 是异步方法的话需要把 resolve 定义到方法的 callback 中
});
});
Promise.all(funcPromise).then(function() {
console.log('all well done');
});
更爽一点可以直接用 async/await
var funcs = [func1, func2, func3];
(async () => {
for(let i=0;i await funcs[i]();
console.log('func'+(i+1)+' well done');
}
console.log('all well done');
})()
// 构建队列
function queue(arr) {
var sequence = Promise.resolve()
arr.forEach(function (item) {
sequence = sequence.then(item)
})
return sequence
}
// 执行队列
queue([a, b, c])
.then(data => {
console.log(data)// abc
})
实现跨项目间的数据访问
将需要的数据存到本地存储中,Vue从本地存储中读取数据
传统的做法是可以在cookie里面的domain属性设置需要跨域的域名,这样就可以在多个站点实现共享cookie,也就是可以通过这种方式共享登录状态。这种方式比较简单快捷,但是有一个缺陷就是,共享cookie的站点需要是同一个顶级域名
在我的域名下登录后,跳转到另一个需要共享登录状态的域名时,可以将token一起携带过去,这样,目标站点获取到携带的token后存储下来,这样就算是实现共享登录状态了。但是这样也有一个缺陷,那就是假如在xx1站点下登录了,有token,如果不通过xx1站点的链接跳转到xx2站点,而是直接访问xx2站点,这样就无法把token携带过去了
export function setToken(token) {
return Cookies.set("token", token, { domain: ['localhost', '127.0.0.1'].includes(document.domain) ? document.domain:".xxx.com"
}
使用webpack的proxy代理
devServer: {
disableHostCheck: true, // 如果存在host、hosts的交叉请求,则需要忽略webpack中host检查
proxy: {
'api/': {
target: 'www.bbb.com', // 最终请求的目标host
changeOrigin: true, // 默认false,保存原host的header
secure: false // 默认true时,不接受https协议且证书无效的后端服务器
}
}
}
这种方案由于不检查请求头协议,会存在CSRP攻击,只适合开发阶段使用。
在多个应用系统中,只需要登录一次,就可以访问其他相互信任的应用系统。
单点登录英文全称Single Sign On,简称就是SSO。
同域下的单点登录
一个企业一般情况下只有一个域名,通过二级域名区分不同的系统。比如我们有个域名叫做:a.com,同时有两个业务系统分别为:app1.a.com和app2.a.com。我们要做单点登录(SSO),需要一个登录系统,叫做:sso.a.com。
我们只要在sso.a.com登录,app1.a.com和app2.a.com就也登录了。通过上面的登陆认证机制,我们可以知道,在sso.a.com中登录了,其实是在sso.a.com的服务端的session中记录了登录状态,同时在浏览器端(Browser)的sso.a.com下写入了Cookie。那么我们怎么才能让app1.a.com和app2.a.com登录呢?这里有两个问题:
Cookie是不能跨域的,我们Cookie的domain属性是sso.a.com,在给app1.a.com和app2.a.com发送请求是带不上的。 sso、app1和app2是不同的应用,它们的session存在自己的应用内,是不共享的。
那么我们如何解决这两个问题呢?针对第一个问题,sso登录以后,可以将Cookie的域设置为顶域,即.a.com,这样所有子域的系统都可以访问到顶域的Cookie。我们在设置Cookie时,只能设置顶域和自己的域,不能设置其他的域。比如:我们不能在自己的系统中给baidu.com的域设置Cookie。
Cookie的问题解决了,我们再来看看session的问题。我们在sso系统登录了,这时再访问app1,Cookie也带到了app1的服务端(Server),app1的服务端怎么找到这个Cookie对应的Session呢?这里就要把3个系统的Session共享,共享Session的解决方案有很多,例如:Spring-Session。这样第2个问题也解决了。
同域下的单点登录就实现了,但这还不是真正的单点登录。
vue-scrollto
ES6中Object.assign()
方法
对象合并
Object.assign 方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象上。
如下代码演示:
对于null, undefined 来说 无法转换成Object,就会在控制台下报错
Object.assign(null); // 报错
Object.assign(undefined); // 报错
对象合并,如果源对象是null的话或者是undefined的话,那么对象合并的时候不会报错,直接会跳过该对象的合并,直接返回目标对象。
var obj = {a: 1};
console.log(Object.assign(obj, null) === obj); // true
console.log(obj); // {a: 1}
var obj = {a: 1};
console.log(Object.assign(obj, undefined) === obj); // true
console.log(obj); // {a: 1}
如果是数值,布尔型,和字符串合并对象的话,都不会报错,但是字符串会以数组的形式表现。
先看数值合并对象如下代码:
var obj = {a: 1};
console.log(Object.assign(obj, 12) === obj); // true
console.log(obj); // {a: 1}
布尔型合并对象如下代码:
var obj = {a: 1};
console.log(Object.assign(obj, true) === obj); // true
console.log(obj); // {a: 1}
字符串合并对象如下代码:
var obj = {a: 1};
console.log(Object.assign(obj, "bcd") === obj); // true
console.log(obj); // {0: 'b', 1: 'c', 2: 'd', a: 1}
如上代码,只有字符串和对象合并,这是因为只有字符串有包装对象,会产生可枚举类型属性
因此Object.assign方法是浅复制,不是深复制,也就是说,如果源对象某个属性的值是对象,那么目标对象拷贝的是这个对象的引用。
比如如下代码:
var o1 = {a: {b: 1} };
var o2 = Object.assign({}, o1);
o1.a.b = 2;
console.log(o2.a.b); // 2
[Object.freeze()]
Object.freeze() 方法用于冻结对象,禁止对于该对象的属性进行修改
可以冻结一个对象。一个被冻结的对象再也不能被修改;冻结了一个对象则不能向这个对象添加新的属性,不能删除已有属性,不能修改该对象已有属性的可枚举性、可配置性、可写性,以及不能修改已有属性的值。此外,冻结一个对象后该对象的原型也不能被修改
Object.freeze()接受一个对象作为参数,并返回一个相同的不可变的对象。这就意味着我们不能添加,删除或更改对象的任何属性。 Object.freeze()是使对象具有不可变性。
Object.freeze() 是“浅冻结”
Object.freeze( )
阻止Vue无法实现 响应式系统
当一个 Vue 实例被创建时,它向 Vue 的响应式系统中加入了其 data 对象中能找到的所有的属性。当这些属性的值发生改变时,视图将会产生“响应”,即匹配更新为新的值。但是如果使用 Object.freeze(),这会阻止修改现有的属性,也意味着响应系统无法再追踪变化。
[formData参数]
FormData 和 Content-Type: multipart/form-data
FrmData类型其实是在XMLHttpRequest 2级定义的,它是为序列化表以及创建与表单格式相同的数据(当然是用于XHR传输)提供便利。 form-data:就是http请求中的multipart/form-data,它会将表单的数据处理为一条消息,以标签为单元,用分隔符分开。既可以上传键值对,也可以上传文件。当上传的字段是文件时,会有Content-Type来表名文件类型;由于有boundary隔离,所以multipart/form-data既可以上传文件,也可以上传键值对,它采用了键值对的方式,所以可以上传多个文件。
var form = new formData();
此时可以调用append(key, value)方法往form实例里边添加数据。
首先,我们要明确formData里面存储的数据形式,一对key/value组成一条数据,key是唯一的,一个key可能对应多个value。如果是使用表单初始化,每一个表单字段对应一条数据,它们的HTML name属性即为key值,它们value属性对应value值。
form.append('checked', 'A')
form.append('checked', 'B')
form.append('userName', 'Susan')
// 此时的form键值对应该是:
// checked: ['A', 'B']
// userName: 'Susan'
传送的数据封装在一个命名为objToFormData的方法中。代码如下:
/**
* objToFormData
* @param {Object}
* @returns {formData}
* 将接口参数转换为formData格式
*/
export const objToFormData = (obj) => {
let data = new FormData()
for (let i in obj) {
data.append(i, obj[i])
}
return data
}
async 函数
async 函数的返回值是 promise 对象
因为async函数的返回值是promise对象( Promise.resolve(value) ),所以要通过then()函数来获取 value 值
注意调用then()函数的是async函数的返回值 asyncFun() ,必须加括号
使用 await 之后,不需要调用 then() 函数,可以直接得到 Promise.resolve(value) 的 value 值
使用 try … catch 捕获异常,这种方法在 async 函数中使用
使用 Promise.catch() 捕获异常,这种方法在 async 函数外使用
❤️关注+点赞+收藏+评论+转发❤️,原创不易,鼓励笔者创作更好的文章
点赞、收藏和评论
我是Jeskson
,感谢各位人才的:点赞、收藏和评论,我们下期见!(如本文内容有地方讲解有误,欢迎指出☞谢谢,一起学习了)
我们下期见!
github
收录,欢迎Star
:https://1024bibi.com
如果觉得这篇文章对你有帮助,希望可以点个在看点个赞,感谢大家~~