哪吒前端周刊 | 第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",将事件监听器继续向下传递

浏览器渲染过程如下

  1. 解析HTML,生成DOM树,解析CSS,生成CSSOM树

  2. 将DOM树和CSSOM树结合,生成渲染树(RenderTree)

  3. Layout(回流):根据生成的渲染树,进行回流(Layout),得到节点的几何信息(位置,大小)

  4. Painting(重绘):根据渲染树以及回流得到的几何信息,得到节点的绝对像素

  5. Display:将像素发送给GPU,展示在页面上。

为了构建渲染树,浏览器主要完成了以下工作:

  1. 从DOM树的根节点开始遍历每个可见节点。

  2. 对于每个可见的节点,找到CSSOM树中对应的规则,并应用它们。

  3. 根据每个可见节点以及其对应的样式,组合生成渲染树。

第一步中,既然说到了要遍历可见的节点,那么我们得先知道,什么节点是不可见的。不可见的节点包括:

  • 一些不会渲染输出的节点,比如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
<template>
<img :src="showImage"
             alt=""
             @load="imgLoad" />
</template>


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

这是一款简单高效的拼音匹配引擎,它能使用拼音够快速的检索列表中的数据。

  1. 使用索引以及缓存机制,从而在客户端实现毫秒级的数据检索

  2. 它的字典数据格式经过压缩处理,简体中文版本仅仅 17kb 大小(Gzip)

  3. 支持多音字、支持拼音首字母匹配

  4. 简体版本覆盖 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()

d8efe75801099241ed89a1ffcfea978a.png
image.png

JS中find方法

用法

find() 方法返回通过测试(函数内判断)的数组的第一个元素的值。

如果没有符合条件的元素返回 undefined

find() 对于空数组,函数是不会执行的。

find() 并没有改变数组的原始值。

array.find(function(currentValue, index, arr),thisValue),其中currentValue为当前项,index为当前索引,arr为当前数组
f061b0f6fdbe3e7cbd101c724052b2dd.png
1636532033(1).png

[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中文文档

ff5ca37d001057ceb4c5b703d6e66f92.png
image.png
2f0b62d72fe6de1692e04e56969d50a3.png
image.png

set方法支持的属性

  1. expires
    定义有效期。如果传入Number,那么单位为天,你也可以传入一个Date对象,表示有效期至Date指定时间。默认情况下cookie有效期截止至用户退出浏览器。

  2. path
    string,表示此cookie对哪个地址可见。默认为”/”。

  3. domain
    string,表示此cookie对哪个域名可见。设置后cookie会对所有子域名可见。默认为对创建此cookie的域名和子域名可见。

  4. 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<funcs.length;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

5d8ba74680874e838519f0a778a8e839.png
image.png

ES6中Object.assign() 方法

对象合并
Object.assign 方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象上。
如下代码演示:

4f2078726f828002f443d2efa04070cd.png
image.png

对于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() 是“浅冻结”

83d4f6eef3b1aa44b3efe7111d8d1d17.png
image.png

Object.freeze( ) 阻止Vue无法实现 响应式系统

当一个 Vue 实例被创建时,它向 Vue 的响应式系统中加入了其 data 对象中能找到的所有的属性。当这些属性的值发生改变时,视图将会产生“响应”,即匹配更新为新的值。但是如果使用 Object.freeze(),这会阻止修改现有的属性,也意味着响应系统无法再追踪变化。

[formData参数]

FormData 和 Content-Type: multipart/form-data

  1. FrmData类型其实是在XMLHttpRequest 2级定义的,它是为序列化表以及创建与表单格式相同的数据(当然是用于XHR传输)提供便利。

  2. 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 函数外使用

9fab7ed8ed53287e7e423df48656221c.png
image.png

❤️关注+点赞+收藏+评论+转发❤️,原创不易,鼓励笔者创作更好的文章

点赞、收藏和评论

我是Jeskson,感谢各位人才的:点赞、收藏和评论,我们下期见!(如本文内容有地方讲解有误,欢迎指出☞谢谢,一起学习了)

我们下期见!

github收录,欢迎Star:https://1024bibi.com

如果觉得这篇文章对你有帮助,希望可以点个在看点个,感谢大家~~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值