Blog

Yoki


  • 首页

  • 归档

  • 搜索

使用set数据结构

发表于 2018-04-22

使用 set 数据结构

set 是一个类似数组的数据结构,但是它没有重复值

如何生成 set 数据结构

  • 直接 new Set()
  • 往 new Set()传入具有 iterator 接口的数据结构,比如数组
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 例一
const set = new Set([1, 2, 3, 4, 4]);
[...set];
// [1, 2, 3, 4]

// 例二
const items = new Set([1, 2, 3, 4, 5, 5, 5, 5]);
items.size; // 5

// 例三
const set = new Set(document.querySelectorAll("div"));
set.size; // 56

// 类似于
const set = new Set();
document.querySelectorAll("div").forEach(div => set.add(div));
set.size; // 56

set 实例属性和方法

属性

  • Set.prototype.constructor:构造函数,默认就是 Set 函数
  • Set.prototype.size:返回实例的总数

方法

操作方法
  • add(val):添加某个值,返回 该实例
  • delete(val):删除某个值,返回布尔值,表示是否成功
  • has(val):返回布尔值,表示是否拥有
  • clear():清空所有,返回值 void
遍历方法

set 结构是具有 itrator 接口的,可以直接 for..of 遍历,相当于下面的 values

  • keys:返回键名的遍历器对象
  • values:返回值的遍历器对象
  • entries:返回键值对的遍历器对象
  • forEach:使用回调函数遍历每个成员

由于 set 没有键名,只有键值,或者说键名和键值一样,所以 keys===values

应用

扩展运算符(…)内部使用 for..of,所以也可以应用在 set 结构。同时数组的 map 和 filter 方法也可以应用于 set。

1
2
3
4
5
6
7
8
9
10
let arr = [3, 5, 2, 2, 5, 5];
let unique = [...new Set(arr)];
// [3, 5, 2]
let set = new Set([1, 2, 3]);
set = new Set([...set].map(x => x * 2));
// 返回Set结构:{2, 4, 6}

let set = new Set([1, 2, 3, 4, 5]);
set = new Set([...set].filter(x => x % 2 == 0));
// 返回Set结构:{2, 4}
  • set 结构很容易实现交集,并集,差集
1
2
3
4
5
6
7
8
9
10
11
12
13
14
let a = new Set([1, 2, 3]);
let b = new Set([4, 3, 2]);

// 并集
let union = new Set([...a, ...b]);
// Set {1, 2, 3, 4}

// 交集
let intersect = new Set([...a].filter(x => b.has(x)));
// set {2, 3}

// 差集
let difference = new Set([...a].filter(x => !b.has(x)));
// Set {1}

浅谈闭包

发表于 2018-04-21

闭包是什么

词法作用域

先要了解什么是词法作用域

  • 作用域是由书写代码时候函数的位置决定的
  • 可以想象函数里面嵌套函数,最里面函数可以方面函数的变量
  • 也就是说内部函数可以读取外部函数的变量
  • 但是我们外部怎么访问内部的局部变量,这就需要闭包
1
2
3
4
5
6
7
8
9
10
//如果我们在外部想拿到n的变量,可以函数内部返回
//
function f1() {
let n = 999;
function f2() {
return n;
}
return f2;
}
let n = f1()();
  • 函数可以记住并访问它所在的词法作用域,换言之,它记住了定义时候的位置
  • 然后它在其他作用域执行的时候,带上了它的词法作用域,这就是闭包

闭包可以干什么

很多高级应用都需要用到闭包来实现,主要用处有三个

  • 上面提到的,可以访问内部函数变量
  • 保持内部变量在内存之中
  • 私有变量和模块化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
function f1() {
var n = 999;

nAdd = function() {
n += 1;
};

function f2() {
alert(n);
}

return f2;
}

var result = f1();

result(); // 999

nAdd();

result(); // 1000
//私有变量和模块化
var monitor = (function() {
var imgs = [];
return function(src) {
var img = new Image();
imgs.push(img);
img.src = src;
};
})();
  • 因为 f1 是 f2 的父函数,f2 依赖于 f1 的存在,f2 被赋给了全局变量,所以垃圾回收机制不会回收
  • 因为 f2 不回收,所以 f1 也会存在于内存之中
  • n 的变量就静静呆在内存之中
  • 比如 add(1)(2)(3)=6 就是这样解的

闭包的问题

  • 闭包会导致变量都保存在内存里面,所以大量闭包会导致性能问题。遇到可以不用的,设置为 null,通知垃圾回收机制回收。

浅谈Iterator接口

发表于 2018-04-21

简单介绍 Iterator(遍历器)

为不同的数据结构提供统一的访问机制,只要数据结构有这种接口,就可以用 for..of 完成遍历

遍历过程

1
2
3
4
5
let it = makeIterator(["a", "b"]);

it.next(); // { value: "a", done: false }
it.next(); // { value: "b", done: false }
it.next(); // { value: undefined, done: true }
  • 第一次调用 next 方法返回数据结构的第一个成员,第二次就第二个直到没有

部署 Iterator 接口

当使用 for..of 遍历某种数据结构,就会自动去寻找 Iterator 接口

  • es6 规定改接口默认部署在Symbol.iterator属性里面
  • 返回一个遍历器对象,该对象的本质特征就是具有 next 方法
  • 每次调用 next 方法,都会返回一个当前成员的信息对象
1
2
3
4
5
6
7
8
9
10
11
12
13
//当执行for..of,会自动执行Symbol.iterator这个函数
const obj = {
[Symbol.iterator]: function() {
return {
next: function() {
return {
value: 1,
done: true
};
}
};
}
};

有些数据结构,本身就具有该接口

  • Array
  • Map
  • Set
  • String
  • TypedArray
  • 函数的 arguments 对象
  • NodeList 对象

对象没有部署该接口

  • 无法确定哪个属性先后
  • 如果实在要遍历,可以使用 map 结构

使用map数据结构

发表于 2018-04-21

object 和 map

object 映照现实生活的物体,但是想使用 key-value 的遍历结构最好使用 map 数据结构

  • object 结构实际上是字符串—值的结构
  • map 具有 iterator 接口,并且是值-值对映射,也就是 key 可以是 function,obj 等
1
2
3
4
5
const data = {};
const element = document.getElementById("myDiv");
//由于对象只接受字符串为键名,所以element被自动转化字符串
data[element] = "metadata";
data["[object HTMLDivElement]"]; // "metadata"

生成 map 结构

  • new Map()
  • 往 new Map()里面传入任何具有 iterator 接口的数据结构,包括数组等,甚至包括 map 本身

需要注意的是,map 的 key 存的是内存地址,也就是说[‘a’]和[‘a’]不相等。基础值才相等

1
2
3
4
5
6
7
8
9
10
11
12
const map = new Map();

map.set(["a"], 555);
map.get(["a"]); // undefined

const k1 = ["a"];
const k2 = ["a"];

map.set(k1, 111).set(k2, 222);

map.get(k1); // 111
map.get(k2); // 222

实例属性和方法

  • size:返回成员总数
  • set(key, value):返回该实例,如果 key 有值会更新,没有就新生成
  • get(key):找到就返回,没有就 undefined
  • has(key):布尔值
  • delete(key):删除成功返回 true,失败 false
  • clear:清除所有成员,没有返回值

遍历方法与 set 类似,但是没有 filter 和 map

  • 比较有意思的是,map 结构默认的 iterator 接口就是 entries 方法
1
2
3
4
5
6
7
8
9
10
11
12
for (let [key, value] of map.entries()) {
console.log(key, value);
}
// "F" "no"
// "T" "yes"

// 等同于使用map.entries()
for (let [key, value] of map) {
console.log(key, value);
}
// "F" "no"
// "T" "yes"

应用

与其他数据结构的互相转换

  • map 转为数组,使用…运算符
1
2
3
const myMap = new Map().set(true, 7).set({ foo: 3 }, ["abc"]);
[...myMap];
// [ [ true, 7 ], [ { foo: 3 }, [ 'abc' ] ] ]
  • 数组转为 map,观察数组结构
1
2
3
4
5
new Map([[true, 7], [{ foo: 3 }, ["abc"]]]);
// Map {
// true => 7,
// Object {foo: 3} => ['abc']
// }

过滤和遍历,但是 map 本身没有 map 和 filter 方法,先转为数组,再塞回 map

1
2
3
4
5
6
7
8
9
10
const map0 = new Map()
.set(1, "a")
.set(2, "b")
.set(3, "c");

const map1 = new Map([...map0].filter(([k, v]) => k < 3));
// 产生 Map 结构 {1 => 'a', 2 => 'b'}

const map2 = new Map([...map0].map(([k, v]) => [k * 2, "_" + v]));
// 产生 Map 结构 {2 => '_a', 4 => '_b', 6 => '_c'}

浅谈TCP

发表于 2018-04-19

TCP

tcp 协议对应于传输层,而 HTTP 协议对应于应用层。

  • http 协议基于 tcp 协议

TCP/IP 协议分层模型

  • 物理层将二进制的 0 和 1 和电压高低,光的闪灭和电波的强弱信号进行转换
  • 链路层代表驱动
  • 网络层
    • 使用 IP 协议,IP 协议基于 IP 转发分包数据
    • ip 协议是个不可靠协议,不会重发
    • ip 协议发送失败会使用 icmp 协议通知失败
    • ARP 解析 ip 中的 mac 地址,mac 地址由网卡出厂提供
    • ip 还隐含链路层的功能,不管双方底层的链路层是啥,都能通信
  • 传输层
    • 通用的 tcp 和 udp 协议
      • tcp 协议面向有连接,能正确处理丢包,传输顺序错乱的问题,但是为了建立与断开链接,需要至少七次的发包收包,资源浪费
      • udp 面向无连接,不管对方有没有收到,如果要得到通知,需要通过应用层
  • 会话层
    • tcp/ip 分层中,会话层,表示层,应用层集中在一起
    • 网络管理通过 snmp 协议

TCP 三次握手,建立连接

  • 客户端-发送带有 syn 标志的数据包——一次握手-服务端
  • 服务端-发送带有 syn/ack 标志的数据包——二次握手-客户端
  • 客户端-发送带有 ack 标志的数据包-三次握手-服务端

TCP 四次挥手,断开连接

  • 客户端-发送一个 fin,用来关闭客户端到服务器的数据传送
  • 服务器-收到这个 fin,发回一个 ack,确认序号为收到的序号为 1。和 syn 一样,一个 fin 将占用一个序号
  • 服务器-关闭与客户端的连接,发送一个 fin 给客户端
  • 客户端-发回 ack 报文确定,并将确认序号设置为收到序号加 1

TCP 和 UDP

  • tcp 就好比在微信聊天,你说一句我说一句
  • udp 就好比我找你,直接就打电话,简单粗暴

浅谈各种数据结构

发表于 2018-04-17

列表,栈,队列,链表,字典,散列,图,二叉查找树

列表

  • 日常生活中:购物清单,待办事项列表等
  • 数据结构较为简单
  • 不需要在一个长序列中查找元素,或者对其进行排序

栈

  • 栈是一种特殊的列表,只能通过列表的一端进行访问,这一端被称为栈顶
  • 餐厅里的盘子叠在一起,从最上面取盘子,洗完也只能放在上面
  • 所以他是先进后出,后入先出的高效数据结构,因为数据只能在栈顶添加或删除,所以操作很快
  • 比如函数调用栈

队列

  • 队列也是一种特殊的列表
  • 只能在队尾添加元素(入队),只能在队头删除元素(出队)
  • 我们在银行排队,排在最前面的人第一个办理业务,然后出去

链表

  • 链表也是一种特殊的列表
  • 每个节点存下个节点的指针
  • 有双向链表,单向链表,循环链表

字典

  • 键值对的存储结构

散列

  • (散列)哈希表是一种常用的数据结构,散列后的数据可以快速插入
  • 在散列表上插入,删除都非常快,但是查找效率非常低
  • 查找一组数组中的最大值和最小值,一般要求助于其他数据结构,比如二叉查找树
  • 即使一个高效的散列函数,也可能将两个键映射为同一个值的可能,这种现象叫碰撞。常见碰撞的处理方法:开链法和线性探测法

图

  • 图是由边的集合和顶点的集合组成
  • 两个城镇由某种道路相连,每个城镇是一个顶点,道路就是边
  • 顶点也有权重,也叫成本
  • 如果一个图的顶点对是有序的:有向图==流程图
  • 如果无序,就是无序图
  • 搜索图的算法:深度优先搜素和广度优先搜索

二叉树和二叉查找树

  • 树是一种非线性结构,以分层的方式存储数据。
  • 二叉树每个节点的子节点不允许超过两个。
  • 一个父节点的两个子节点分别称为左节点和右节点,通过将子节点的个数限定为 2 个,可以高效插入,查找,删除。
  • 二叉查找树(BST)是一种特殊得二叉树,相对较小的值保存在左节点中,较大的值保存在右节点中。这一特性使得查找的效率很高。

js性能填坑之路

发表于 2018-04-15

简单介绍

  • javascript 是解释性语言,性能很慢,随着 web 在生活中的普及,这点性能显然不够用。你能想象双十一的一个按钮点半天才有反应嘛?所幸很多大神,都在不断的改进这个性能问题

漫漫填坑路

JIT

google 在 09 年 v8 引擎里引入 just in time compiling(即时编译),跟之前 ajax 出现引起的轰动是一样的,性能瞬间就上升了 20-40 倍的速度。

  • 从此网页进入了网页应用的时代

解释器和编译器

  • 解释器是一行行解释代码,性能会下降。编译器是把源代码编译成目标代码。
  • 解释器是叫他干什么立刻就去干什么,比如买菜,马上去市场回来,再去买油又马上市场
  • 编译器是叫他干什么直到说完才去干什么,去买菜,买油统一去市场
  • 但是 jit 是基于运行期编译,js 是一个没有类型的语言,所以 jit 大多数时候都在猜测 js 的类型。
1
2
3
4
5
6
function add(a, b) {
return a + b;
}
add(1, 2); //这时候jit就把a和b确认为int类型
//然后你这样做
add("hi", "yoki"); //已经编译成机器码了,但是又变成字符串类型,只能推倒重来,有时候jit的性能提升,还没有这个重编的开销大

TypeScript

  • 既然 jit 大多数时候在猜测类型,那索性搞个强类型语言,再编译成 js 就行了

asm.js

  • ts 是 js 的超集,这个是语法兼容 js 的汇编语言,是 js 的子集
  • 通过标注声明类型,而且不得改变,这样就节省了类型判断的时间

WebAssembly

  • 一种二进制码的规范,是比 asm.js 更为激进的东西
  • 理论上所有语言都可以编译成这个,然后泡在浏览器上
  • 但是目前还摸不了 dom

浅谈重排和重绘

发表于 2018-04-10

简单介绍

  • 网页生成布局(重排 reflow),页面绘制(重绘 repaint)一个个盒子元素

    重排一定会引起重绘,重绘不一定引起重排

  • 为什么这样说,如果我改变了布局,比如改变边框宽度或者给段落增加文字,浏览器需要重新计算元素的几何属性,就会重排,然后就会重新绘制
  • 但是如果改变元素的背景色,就只会发生重绘,而不会影响布局
  • 重绘和重排非常消耗资源,应该尽量避免

怎样优化

批量读或者批量写

现代浏览器已经很智能,会把所有变动排在一起,然后一次性集中在一起,推入到一个队列然后再渲染,这样通过队列话修改然后批量执行可以优化重排过程。

1
2
3
4
5
6
7
8
9
div.stryle.color = "red";
div.style.marginTop = "30px";
//这里不会是一次次变,而是只会触发一次重排和重绘

//但是下面这样,就会触发两次重排和重绘
div.style.color = "blue";
var margin = parseInt(div.style.marginTop);
div.style.marginTop = margin + 10 + "px";
//第二行中,我们读取该div的元素位置,这个时候浏览器这个队列马上就要推出去渲染,才可以得知新的布局的位置,所以浏览器必须要重排
  • 由上可知,如果我们在写操作之后,再读会立即渲染,有以下读操作
1
2
3
4
offsetTop / offsetLeft / offsetWidth / offsetHeight;
scrollTop / scrollLeft / scrollWidth / scrollHeight;
clientTop / clientLeft / clientWidth / clientHeight;
getComputedStyle();
  • 所以我们要懂得批量读和批量写
1
2
3
4
5
6
7
8
9
// bad
div.style.left = div.offsetLeft + 10 + "px";
div.style.top = div.offsetTop + 10 + "px";

// good
var left = div.offsetLeft;
var top = div.offsetTop;
div.style.left = left + 10 + "px";
div.style.top = top + 10 + "px";

合并样式操作

尽管现代浏览器做了优化,但是旧的浏览器可能没有,所以我们应该尽可能合并对样式的修改

  • cssText:el.style.cssText=’border-left: 1px; border-right: 2px; padding: 5px;’
  • 修改 className:如果不是那么依赖计算,可以通过修改 className

修改 dom 元素的时候使其脱离文档流

当我们需要对 dom 元素频繁操作的时候,我们可以这样减少重排和重绘

  • 让该 dom 元素脱离文档流(重排)
  • 操作元素
  • 完成后再添加到 document(重排)

如何脱离文档流

  • 隐藏元素:display:none,然后一系列操作后,再显示
  • 推荐:使用 document fragment,就是传说中的文档碎片了,document.createDocumentFragment(),另外的 dom 子树,操作完后,再拷贝回来文档中,div.appendChild(fragment)
  • 将原始元素拷贝到一个脱离文档的节点中,cloneNode,修改这个副本,然后再通过这个元素的父元素的 replaceChild 替换他
1
2
3
4
let old = document.getElementById("mylist");
let clone = old.cloneNode(true);
appendDataToElement(clone, data);
old.parentNode.replaceChild(clone, old);

缓存读过的样式结果

  • div.style.top 读取可能会重排,这样最好这个缓存到变量里面,下次要用到这个就不用再读 div 的信息了

定位脱离文档流

  • 可以将要多次重排的元素,position 可以设置 abs 和 fixed,这样就脱离文档流,不会影响其他元素。
  • 有动画效果的可以设置为绝对定位

使用 window.requestAnimationFrame()、window.requestIdleCallback() 重新渲染

vue的一些问题集合(二)

发表于 2018-04-05

再写一波

‘@/components/‘的@

  • webpack 可以配置 alias 配置别名,这个@一般是 src 目录的别名

npm run dev 报端口已经在运行

  • 可能你在运行其他项目,去修改一下默认端口

父组件怎么调用子组件的方法

  • this.$refs 里面找到该实例,前提是先设置了 ref
  • this.$children 里面找到该实例

nginx 怎么配置

一大波不靠谱的 nginx 配置

v-if 和 v-show

v-if 是 dom 没有插入到文档里面,等条件成立,才 append 到 document 里面。

  • 等拿到值才可以处理组件内部的逻辑的,不然会报错
  • 有些 v-for 但是还没有拿到值的

v-show 只是简单通过 css 隐藏了

  • 不会导致页面重绘,改善用户体验

axios 的请求后台不接受

  • axios 默认是 json 格式提交,如果后台只支持表单序列化,就要自己转义
  • axios 有个 qs 模块可以试试

css 的 scoped 有什么功能

  • 给每个类或者 id 自动添加 hash,只能给该组件用,不同组件不能进行继承
1
2
3
4
5
/ 写的时候是这个
.a{}

// 编译过后,加上了 hash
.a[data-v-1ec35ffc]{}

小图片渲染出来 base64

  • webpack 的 url-loader 处理,对于小于多少 k 一下的图片转成 base64 内联到 html
  • 减少 http 请求降低服务器负担
  • 网络不好的时候,内联的内容先加载,优化体验

ERROR in static/js/xxxxxxx.js from UglifyJs

  • 引入压缩后的 js,webpack 又开启了 uglifyjs 压缩,报错

keep-alive 缓存组件后很慢

  • 因为都是保存在内存里面的,一旦大量组件缓存性能肯定不会好

vue的一些问题集合(一)

发表于 2018-04-02

在公司一直用 vue 开发,有时候被一些小伙伴经常问到的问题,这里收集一下勉强再挖个坑

npm install 安装超时

  • cnpm:npm i cnpm -g
  • yarn 和 npm 改成淘宝源
1
2
npm config : npm config set registry https://registry.npm.taobao.org
yarn config : yarn config set registry https://registry.npm.taobao.org

data functions should return an object

这是因为你肯定在书写单组件,而单组件的 data 必须返回一个单例,可以参见data 必须是函数

  • 这里简单解释下,如果一个组件 data 引用的是一个同一个对象指针,那么当你在另外一个组件应用这个组件,v-for 他的时候,比如三个,那么你修改其中一个组件,另外两个这个组件的状态也会改变,但是我们的原则是组件间相互隔离,互不影响。

在函数内写了 this.name(data 里存在 name),却报错 Cannot set property ‘xxx’ of undefined

你期待 this 指向 vue 实例,实际上它指的是 window,this 和当前上下文有关

  • promise 的回调
  • setInterval

比较好的解决方案就是使用箭头函数,绑定当前对象为 this 的上下文

Component template shold contain exactly one root element.If you are useing v-if on multiple elements

  • 单组件文件里必须要有个根元素,一个大 div

No ‘Access-Control-Allow-Origin’ header is present on the requested resource.

跨域问题

  • CORS 前后端配置
  • nginx 反向代理,一般线上使用
  • 本地开发,脚手架 vue-cli 里有 proxyTable
1
2
3
4
5
6
7
8
9
10
11
12
// target : 就是 api 的代理的实际路径
// changeOrigin: 必须是变源
// pathRewrite : 路径重定向
proxyTable: {
"/api": {
target: "http://yoki.com",
changeOrigin: true,
// pathRewrite: {
// "^/api": "/"
// }
}
}

数组里的值手动更新了,为什么视图不更新

this.arr[0]=1,但是视图没更新

  • 这是因为 vue 重写了数组的原生方法,使用了数组方法 push,pop 等,会对其观察,并响应到视图。究其原因是 js 无法检测到到数组的变化
  • 可以使用 this.$set
  • 可以数组的方法更改

为什么根目录下会有 lock 文件

先简单介绍一下 A.B.C:A 是 major 版本表示和上个版本不兼容的大更改,B 是 minor 版本增加了新功能,可以向后兼容,C 是 patch 版本修复了 bug 向后兼容

  • npm 上包那么多,说不定什么时候就会更新,根据 package.json 里的^(会更新到 major 版本号最新的),~(会更新到 minor 版本最新的)
  • 有些包甚至不遵循发包规范
  • 所以我们需要统一版本号,这样不同的电脑在不同的时间就会下载到一样的包了

npm i -S 和-D 的区别

  • -S 会放到 dependencies 里面,存放到线上能访问到的代码,比如 vue,vue-router
  • -D 会放到 devDependcies:处于开发模式所依赖的模块,不产生额外代码到生产环境,比如 babel-core

单页应用没办法做 seo 吗

  • spa 的页面由 javascript 根据 API 发回的数据在用户的浏览器里自动生成的,Baidu 不会看 Javascript,那么页面对于它来说就是一个空洞洞的 html 和很多 scirpt 元素
  • 解决方案:服务端渲染(SSR)可以,请求回来是一个处理好的 html,vue 的服务端渲染方案nuxt

为什么首屏加载这么慢

打包文件文件很大

  • 减少 jq 这种库的引入,现在都是数据驱动
  • webpack 压缩 js,css
  • 路由懒加载,代码分割
  • 采用服务端渲染,可以避免浏览器去解析模板和指令
1…567…9
Yoki

Yoki

云在青天水在瓶

82 日志
21 标签
© 2019 Yoki
由 Hexo 强力驱动
|
主题 — NexT.Pisces v5.1.4