0%

cc-audiobuffer 2.x 版本

Gayhub 源码与2.x文章 喜欢给个Star !

不断完善更新中

1.x版本文章: 文章

更新功能

  • 当前播放语音暂停
  • 当前播放语音继续播放
  • 清空当前的语音队列

使用请看 源码Markdown

源码的升级

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
_playNext() {
const _this = this
if (this._isHasBuffer() && !this._isHasCurrentAudio()) {
this.currentAudio = this._shiftBuffer()
this.currentAudio.play()
} else if (!this._isHasBuffer()) {
this.currentAudio = null
}
if (this._isHasCurrentAudio()) {
this.currentAudio.addEventListener('ended', function() {
_this.currentAudio = null
_this._playNext()
}, false)
}
}

解释

_playNext() 函数无论在一开始播放和暂停之后继续播放都要调用。

如何区分当前时一开始播放还是暂停后的继续播放呢?

利用this._isHasCurrentAudio 判断当前是否有载入语音,如果有就是暂停后继续播放,反之就是一开始的初始播放。

接下来即将要实现的功能

  • 接下来的计划:单例模式、报错处理、暂停播放继续播放清空队列优化

【小玩】cc-audiobuffer 一个切片语音拼接工具

源码在最后,喜欢就给个Star !

萌新跪求优化指导,请Forks

萌新瑟瑟发抖

话说在那么紧张的大三快要找实习了而自己还在玩这些真的好吗?T_T

这个小工具是我在最近解决项目问题中制作的。

背景是这样的:作为前端的萌新,被主程老大们吓得瑟瑟发抖。语音实时通讯!这个是最近项目中的一个里程碑,也是一个难点所在。主程老大们的想法是,浏览器录音,切片,通过服务器转存再通过广播Url的方式进行转发。使得各个客户端都可以实时通讯。

解决方法:大神 muaz-khan WebRTC中的 MediaStreamRecorder 进行浏览器录音和切片(膜拜),使用七牛云 存储,转发并获得切片语音的Url!最后最后,就是拼接语音啦,所以就产生了这个cc-audiobuffer工具。

直接看实现

cc-audiobuffer采用了ES6封装类来实现,并且使用babel转义。

连续播放的实现

  • 递归的思想,只要缓冲区中还有Audio,就继续播放。
  • 事件监听,监听Audio对象中的 ended 事件作为下一段语音播放的标志。
1
2
3
4
5
6
7
8
9
10
11
12
play() {
const _this = this
if (this.isHasBuffer()) {
this.currentAudio = this.shiftBuffer()
this.currentAudio.play()
_this.currentAudio.addEventListener('ended', function () {
_this.play()
}, false)
}else{
this.currentAudio=null
}
}

语音提前加载的实现

  • 在语音的Url加入缓冲区的时候,使用Audio对象的preload属性实现。
1
2
3
let audio = new Audio(url)
audio.preload = "auto"
this.AudioUrlBag.push(audio)

动态语音加载的实现

  • CCAudioBuffer有pushBuffer('url') 方法,如果缓冲区有Audio正在播放的话,此方法将新的Audio加入队列,如果缓冲区中没有Audio播放,将会将Audio加入缓冲区同时自动播放新的Audio。
1
2
3
4
5
6
7
8
9
10
11
12
13
pushBuffer(url) {
if(!this.isHasCurrentAudio()){
let audio = new Audio(url)
audio.preload = "auto"
this.AudioUrlBag.push(audio)
this.play()
}
else{
let audio = new Audio(url)
audio.preload = "auto"
this.AudioUrlBag.push(audio)
}
}

想试一下吗?

安装

npm:

1
npm install cc-audiobuffer
1
import CCAudioBuffer from 'cc-audiobuffer'

script:

1
<script src="dist/cc-audiobuffer.js"></script>

使用

无参数创建

1
2
3
let CCAudioBuffer = new CCAudioBuffer()

CCAudioBuffer.pushBuffer('url')

当有新的url语音地址传过来的时候,CCAudioBuffer会提前加载并且自动播放语音。不管何时调用pushBuffer('url'),这些语音都会依次进入缓冲区且连续播放直到缓冲区为空。

有参数创建

1
2
3
4
5
let CCAudioBuffer= new CCAudioBuffer([new Audio('url'),new Audio('url'),new Audio('url')])

CCAudioBuffer.play()

CCAudioBuffer.pushBuffer('url')

当你有参数创建时,参数规定是Audio对象的一个数组。

接下来需要调用play() 接口保证参数内的Audio播放。

pushBuffer() 的作用和无参数创建一样。

反思一下

最近真是忙成象拔蚌,这个工具做的很急,自我安慰下。

源码很短很短,也没有考虑到对象内部的一些私有封装什么的。还是想以后有时间能重写一下。

也写写些请求啥的,希望请教大家,问问大家有没有优化方案什么的,也给小生我上门课。

最后这里是Gayhub源码

喜欢给个Star!指教给个Forks!

戳我

细胞自动机

备注:文末有自己用Javascript简单实现的网页版细胞自动机(还挺好玩)

什么是细胞自动机

Imgur

细胞自动机(英语:Cellular automaton),又称格状自动机元胞自动机,它是由无限个有规律、坚硬的方格组成,每格均处于一种有限状态。每格于t时的态由t-1时的一集有限格(这集叫那格的邻域)的态决定。每一格的“邻居”都是已被固定的。每次演进时,每格均遵从同一规矩一齐演进。

当然这个细胞自动机有一个游戏实现 ——康威生命游戏(英语:Conway’s Game of Life)

康威生命游戏规则

生命游戏中,对于任意细胞,规则如下:
每个细胞有两种状态-存活或死亡,每个细胞与以自身为中心的周围八格细胞产生互动。(如图,黑色为存活,白色为死亡)

  1. 当前细胞为存活状态时,当周围低于2个(不包含2个)存活细胞时, 该细胞变成死亡状态。(模拟生命数量稀少)
  2. 当前细胞为存活状态时,当周围有2个或3个存活细胞时, 该细胞保持原样。
  3. 当前细胞为存活状态时,当周围有3个以上的存活细胞时,该细胞变成死亡状态。(模拟生命数量过多)
  4. 当前细胞为死亡状态时,当周围有3个存活细胞时,该细胞变成存活状态。 (模拟繁殖)

可以把最初的细胞结构定义为种子,当所有在种子中的细胞同时被以上规则处理后, 可以得到第一代细胞图。按规则继续处理当前的细胞图,可以得到下一代的细胞图,周而复始。

康威生命游戏的自由

有了核心的算法(游戏规则),康威生命就是一个具有生命的自由游戏。你可以在游戏中创造出自己的细胞世界。周而复始。

Conways_game_of_life_breeder_animation

自己实现的一个生命游戏

生命游戏其实并不是很复杂,自己实现一个还是挺好玩的。所以自己就用Vue实现了一个小小的生命游戏

核心思想

  • 下一步要做什么,生命游戏最重要就是下一步,不管游戏规则是如何,下一步状态是这个游戏发展的动力。

    1
    2
    3
    4
    5
    function nextStep(map) {
    let newMap;
    //@TODO对newMap进行核心算法的编写
    return newMap;
    }

    此函数就是游戏的生命,将游戏规则编写进这个函数,Vue只负责渲染这个newMap就好了。

所以我们有以下函数

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
31
function nextStep(map) {
let newMap = Array(map.length).fill(Array(map.length).fill(0)).map((i) => i.map((j) => 0));
let countAlive = 0;
for (let i = 0; i < map.length; i++) {
for (let j = 0; j < map.length; j++) {
countAlive =
getMap(i + 1, j, map) +
getMap(i - 1, j, map) +
getMap(i, j + 1, map) +
getMap(i, j - 1, map) +
getMap(i + 1, j + 1, map) +
getMap(i + 1, j - 1, map) +
getMap(i - 1, j + 1, map) +
getMap(i - 1, j - 1, map);
if (map[i][j] === 0 && countAlive < 3) {
newMap[i][j] = 0;
}
if (map[i][j] === 1 && 2 <= countAlive && countAlive <= 3) {
newMap[i][j] = 1;
}
if (map[i][j] === 1 && (countAlive > 3 || countAlive < 2)) {
newMap[i][j] = 0;
}
if (map[i][j] === 0 && countAlive === 3) {
newMap[i][j] = 1;
}
countAlive = 0;
}
}
return newMap;
}
  • 边界问题,最外层肯定都是没有细胞的,所以我们做了边界判断函数。
1
2
3
4
5
6
function getMap(i, j, map) {
if (i >= map.length || j >= map.length || i < 0 || j < 0) {
return 0;
}
return map[i][j];
}
  • 文件导出导入 当然加了扩展功能,保存自己喜欢的细胞自动机成文件。

  • 速度调整 扩展功能,调整下一步的速度。

实现的效果

img

Github源代码,喜欢就给个Star。细胞自动机

linux下 hexo deploy 显示Permission Denied 的权限不够问题

前面的话

为什么发现了这个问题呢?由于工作的原因,我将所有的工作的机子都变成了ubuntu 16.04,自从用了linux内核真的,腰不酸了腿不疼了,干啥都有活力了,不会像window下各种浪费生命了。

当然首先就是要在linux机子上安装git 这个程序员必备的工具,并且在自己的账户上连接上github 的ssh。

首先就是要将所有的项目在Ubuntu 下跑起来,当然也包括我之前的博客,顺手就apt-get安装了hexo,并通过 hexo项目整体转移的方法,请看链接 更换电脑如何转移hexo

最后发现sudo hexo deploy之后出现了经典的 本地没有ssh 对应github 上面的 ssh的问题。也就是Permission Denied。

思考

为什么会出现permission denied。就是没有ssh 呗。这也是linux权限问题的锅。首先要使用hexo 必须使用sudo获取linux下的最高权限 ,sudo目录就是linux下的root,当你添加ssh的时候都是在自己的账户下添加的,而没有到root去添加,所有sudo hexo deploy当然就会出现ssh不存在的permission denied问题啦。

解决

直接在root下添加ssh不就可以解决了啊。我们来一步一步的解决如何添加ssh到linux root目录下面。

  • sudo ssh-keygen -t rsa -C “xxx@xxx.com”加上sudo在linux的root目录下创建ssh。

  • 然后利用cat 命令 gedit 或者vim打开在root目录下的ssh。sudo cat /var/root/.ssh/id_rsa.pub

  • 在github上面进行ssh的粘贴。方法。github添加ssh

窗口进度条及其高级使用

我们大概实现的效果就像YouTube上面的红色进度条那样。但是YouTube上面那个进度条还是很坑爹的。文章后面再告诉你们为什么。

首先窗口的滚动进度条

窗口的滚动条非常的简单,只要用 window.onscroll 事件的监听,就可以实现。当然我最近在研究Vue.js,所以用vue.js实现了一个。反正都差不多吧,这个没什么好说的。

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
31
32
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Bar</title>
<style>
#bar{
position: fixed;
height: 5px;
background-color: aqua;
}
</style>
</head>
<body id="app">
<div id="bar" v-bind:style="{width:changeWidth+'%'}"></div>
<div v-for="n in 1000">{{message}}</div>
<script src="./vue.min.js"></script>
<script>
var app = new Vue({
el: '#app',
data: {
message: 'This is a Vue bar!',
changeWidth:0
}
})

window.addEventListener('scroll',() => {
app.changeWidth=(document.body.scrollTop/(document.body.scrollHeight-window.innerHeight))*100
})

</script>
</body>
</html>

效果

优化一下

因为这里的窗口滚动进度条没有过度效果吗(虽然谷歌浏览器她会自动帮你优化一点过度的效果,但是我们还是自己写的和谐一点点),所以就加多一句CSS3的动画

1
transition: width 1s;

到这里我们的滚动进度条就基本上实现了,也可以做一个很不错的水平效果。但是这个滚动还是有很多东西值得我们去研究一下的。

window.onscroll什么时候会触发

这里的window.onscroll当滚动了鼠标的滚轮的时候就会触发对吧,这个无可非议。就算这个界面我们手抽搐的去滚动这个滚轮,他就一直一直的触发
这样我们在我们的触发代码里面输入一个console.log(1)

我这里指滚动了一下,这里就被触发了13次,虽然这个鼠标滚轮的时间开销是不大的,也不用特地去做优化,但是如果是一次滚动我们触发了一次服务器请求会怎么样?这个结果我们不敢想象。我们既然是是去深入的挖掘它,那我们就深入去看看可以怎么优化。

我们做一个延迟的滚动进度条

功能:在鼠标一直滚动的时候是不会改变进度条的长度,直到鼠标的滚动停止后0.5s之后才开始出发轮动条的改变,即你一直在0.5s之内滚动,进度条长度不会改变。
在这里我们主要是学习这个方法吧。如果模拟进度条的改变是请求服务器的话,我们就可以有效的去抑制住那些疯狂请求服务器的动作。

我们要实现的效果大概是这样的:

这样我们主要还是用setTimeout来进行限制,因为setTimeout可以有效的帮我们延迟触发的时间。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
window.addEventListener('scroll',delay(() => {
console.log(1);
app.changeWidth=(document.body.scrollTop/(document.body.scrollHeight-window.innerHeight))*100
},500))

function valve(func,time){
let timer=null;
const _fun=function(){
clearTimeout(timer);
timer = setTimeout(()=>{func()}, time)
}
return _fun;
}

在这里我单独做了一个函数,我们利用了setTimeout和clearTimeout来成功抑制住我们的进度条长度的改变,这也使得这个进度条在停止的时候才会进行改变。实现的效果还是不错的哈哈!

当然这种延迟的效果的思想应该是更重要的,我曾今做过一个那个输入框提示的一个小功能,当这个input输入框里面有内容在输入的时候不会触发,当这个input输入停止后0.5s,网页会用这个input里面不管输入还是没有输入的内容去请求服务器,看看是否存在这个用户。或者是一个输入的提示效果

例如这样:

那我们为什么不做一个可以时间间隔触发的进度条呢?

效果是这样的:

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
window.addEventListener('scroll',valve(() => {
app.changeWidth=(document.body.scrollTop/(document.body.scrollHeight-window.innerHeight))*100
},1000))

function valve(func,time){
let timer=null;
const _fun=function(){
if(timer) {

}else{
clearTimeout(timer);
timer =setTimeout(() => {func()}, time);
timer=null;
}
}
return _fun;
}

这个进度条的目的是如果鼠标滚轮一直在滚动,那么他将会做一个阻隔,是隔一秒钟变化一次。这样的话,就有效的减少了进度条频繁的变化,他只是在滚动的途中,隔着一秒钟去变化一次。
这个思想就是阻隔的思想,如果一个事件一直在请求服务器,我就可以限定出它间隔多少秒去请求服务器,有效的阻隔请去服务器的次数。

上面的两个解决方案和思想也是我们这次进度条深究得出的比较有价值的东西

那我们还可以做什么进度条呢?

其实在文章的开头我们就有提到,YouTube上面的红色进度条,这个进度条为什么坑爹呢!因为他是假的。
我们每次打开加载视频的时候,他是不是都会卡一下?见图。

可能大多数人都以为它加载的时候卡了一下吧。其实是假的!假的! 他只是故意营造出一个断断续续加载的效果让大家看的舒服一点而已。

你可以论证一下。你把网断了,你再点视频,发现他还是有这个红色的条子,只是到一般就停了变成了无网络的界面而已。

要是再不信你可以用谷歌浏览器测试下?哈哈!

这里用到就是那个间隔变化的滚动条,可以自己实现一个去模拟Youtube上面的效果哦!

最近在干嘛

老实说我已经有快一个月都没有更新博客了,虽然现在博客浏览量还不是很多啦。但是还是我必要说说最近我在干嘛。

其实最近在一间小的算是创业团队或者公司实习,所以时间比较紧,星期二星期四满课,一三五六都会在办公室打代码,而且回到学校都会被各种作业和部门事情困住,所以呢,就没有很多时间去更新自己的博客或者写一写笔记了。

只不过在那个团队有很牛逼的师兄带着我学习,也接触很多新的技术,例如Vuejs等等,才发现真的其实学习真的是无止境的。

大概这个实习会持续到1月份。这时间里面我可能会一直记录我在学习工作中遇到的问题吧。也希望自己能够进步。

到时候这个项目结束以后,我也会做几期记录,来记下自己学习到了什么,在这几个月做了什么有了什么成果。也给自己一个鼓励吧!

项目中的prop()的attr()的jQuery大坑

说起来惭愧,在现在React横行的年代,自己还在大学的项目里面用jQuery慢慢磨。大一的时候深受《锋利的jQuery》的影响,一直都觉得jQuery是一个特别 Niubility 的框架。当然!这只不过是开个玩笑,受项目制约,我还是得认真研究下这个jQuery。

项目中遇到的程序的Bug

以为这个东西非常简单

首先我的项目要做一个全选复选框的功能:列表项有多个复选框,当列表头的复选框的选择了之后,列表中的所有的复选框不管是否已经选择都必须全部选择。而当列表头的复选框取消了选择,所有的列表复选框也必须取消选择。

看似很简单的功能呢。我们知道在复选框中,只要添加了 checked 属性,就算他里面是” “这样的空值,但是都是选中的。

那就用jQuery的attr()来进行实现吧,将checkbox的 checked 属性变成 checked值。首先我们来实现全选的功能

1
2
3
if ($(".contentRightListGroupBanderListInput").attr("checked")) {
$("input[name=items]:checkbox").attr('checked', true);
}

结果

结果发现:根本没有反应诶亲!而且如果用了FireBug进行调试,发现if的语句内部根本就进不去啊!

利用Firefox进行调试,发现checked属性的确是true,但是都是进不去if语句。

并且我还发现一个更加严重的问题,怎么做全不选的功能,利用hasAttr()来判断是否有checked属性吗?这样如果没有选中的话,就会返回一个undefined,这个恐怖的东西是我们不想见到的。

翻阅一切资料找到的prop()方法

在jQuery 1.6之后,就开始推荐利用prop()方法来部分替代attr()方法了。利用prop()方法的好处,特别是在处理单选复选框的时候,利用prop()代替attr()会更好。

因为prop()在判断checked只会返回true和false,有这个checked属性返回true,没有就返回false,这样有利于我们判断。但attr()返回都是我们自己定义属性值,例如在checked属性中,虽然只要有这属性,选框都是选中,但是他返回的是值,也就不管你里面的值是checked true 还是空值都会返回给你,如果没有checked属性还会返回undefined,简直乱麻。

利用prop方法来判断是否选中的简单代码:

1
2
3
4
5
6
if ($(".contentRightListGroupBanderListInput").prop("checked") == true) {
$("input[name=items]:checkbox").prop('checked', true);
}
if ($(".contentRightListGroupBanderListInput").prop("checked") == false) {
$("input[name=items]:checkbox").prop('checked', false);
}

这样单纯的利用truefalse来判断就可以轻松解决用attr()属性解决不了的问题。

什么时候应该用prop()什么时候用attr()呢

其实如果说prop()只是在checked中有大作用那就错了

1.添加属性名称该属性就会生效应该使用prop();
2.是有true,false两个属性使用prop();

prop()就是专门为这两个来使用的,例如disabled checked selected

那么在其他情况之下就使用attr就可以完成几乎所有功能了。特别是在自定义属性中,还是一定要用attr()的,因为你定义的属性值可能连你自己都不知道你在定义什么,哈哈,开玩笑。总之,自己定义的属性一定要用attr()。

当SetTimeout遇到了字符串

今天闲来没事的时候,去逛逛segementFault,看了看别人提的问题。说到setTimeout和window.onload冲突。一开始我是挺疑惑他表他什么意思的,因为setTimeout和window.onload应该不会有明显的冲突吧。带着疑惑去追问。后来贴出代码的时候我就明白了,来看看他的代码中的疑惑吧。

1
2
3
window.onload=function(){
setTimeout("D.style.background='#990033'",2000);
}

他觉得这个代码运行的时候,setTimeout会报错,不能正确的运行指定的代码,就觉得在window.onload和setTimeout有冲突。

我对这个问题非常感兴趣。也自己试了一下,发现确实不能运行字符串里面的代码。但是还是很怀疑window.onload和setTimeout怎么可能会有冲突呢?

解决问题

为了测试方便,我就稍微改动了一下代码。将setTimeout的调用改成调用函数。

1
2
3
4
5
6
window.onload = function() {
function myFun(i) {
alert(1);
}
setTimeout('myFun(1)', 2000);
}

在这里发现的确不能运行,但是这里的问题就很明显了,我将函数的调用变成了 'myFun(1)'字符串调用,所以会出现不能调用 myFun()的问题。

为了深入理解我们在W3school查询一下setTimeout的用法

那么就是setTimeout只能接受函数或者是表达式的计算。那么现在答案很明显了,既是setTimeout不支持第一个参数为字符串的调用。

但是按照提问者的用法,这种 "D.style.background='#990033'"CSS的调用也必须准守这种形式那么怎么办?

那么就服从setTimeout的规则,说干就干,将他装换成函数就好了。见以下代码。

1
2
3
4
5
window.onload=function(){
setTimeout(function(){
D.style.background='#990033'
},2000);
}

这里的2秒钟之后就会执行setTimeout里面的匿名函数,即准守了setTimeout的原则,也可以利用setTimeout来调用类似字符串的形式的功能代码。

回到提问并且深入挖掘

回到提出的问题上来,深入的挖掘,其实并不是setTimeout和window.onload有冲突,在原来的问题中,其实是Javascript作用域在作怪。

在原先的问题中,如果是 setTimeout("D.style.background='#990033'",2000); 的话,由于setTimeout的第一个参数支持的是函数或者是表达式,所以字符串会被自动执行new Function,将这个字符串强制转换成一个函数。

我们知道在Javascript中,函数里面有自己的作用域,和外界的作用域不同,而在函数内部并没有D.style.background这个对象,所以会有报错。在我变化的例子中,也是如此,如果使用了 setTimeout('myFun(1)', 2000),那么这里面的字符串就会被自动执行new Function,所以创建出来的函数不在这个作用域内,当然也就不能调用,会出现无法找到的问题了。

AngularJS显式依赖注入

依赖注入是Angular的招牌功能,使用angular的$injector注入器就可以实例化所有的组件、模块、指令、控制器。这也是angular的核心功能之一。也是理解Angular内部机制的一部分。

而一般来说Angular是不需要显式进行依赖注入的,因为AngualrJS会内部帮你隐式注入。你可以不管内部发生了什么,但是你的组件、模块、指令、控制器就和声明了ng的元素节点绑定在了一起。

只不过为了了解Angular的内部机制,我们还是要学会如果利用angular的$injector进行手工的显式注入。除此之外Angulard的显式注入可以定义一个函数被调用时用到的依赖关系。这样的话,在源代码被压缩、参数名字改变的时候,angular还是可以执行依赖注入。但是隐式的注入就没有这种效果了。

现在我们来看看AngularJS显式注入的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var aControllerFactory =
function aController($scope, greeter) {
console.log("LOADED controller", greeter);
};

aControllerFactory.$inject = ['$scope', 'greeter'];
var greeterService = function() {
console.log("greeter service");
return {
doTheThing: function methodThatDoesAThing() {}
}//factory返回对象
};

angular.module('myApp', []) //这里的myApp链接到html元素
.controller('MyController', aControllerFactory)
.factory('greeter', greeterService);

var injector = angular.injector(['ng', 'myApp']),
controller = injector.get('$controller'),
rootScope = injector.get('$rootScope'),
newScope = rootScope.$new();

controller('MyController', { $scope: newScope });

这段代码显示的结果是:

显示的依赖注入的参数的顺序是非常重要的,如果$inject数组的顺序和注入的顺序有差别,就会让注入的元素为空。

Angular.js中的compile pre-link post-link选项的个人理解

AngularJS的生命周期

在AngularJS的生命周期中,分为编译链接两个阶段。

  • 在编译阶段中,每一个指令可能有会有另外一个指令,AngularJS遍历他们形成了模板树,之后会返回一个模板函数,而在模板函数返回之前DOM都是没有形成的,所以此时ng-repeat指令就会生效。
    而在编译完成之后,会返回一个编译函数,这个编译函数会返回一个总的将所有子指令模板合并在一起的模板函数,并且交给链接阶段。

  • 在链接阶段中,我们可以将作用域scope和DOM进行链接,并且对每一个函数的模板的实例进行链接或者监听器的注册。

这个阶段中的三个选项 compile pre-link post-link

这个三个指令并不是完全能用得到,特别是compile,这个指令在实践中,并不会频繁的使用。但是理解这个三个指令的工作机制对于我们理解AngluarJS的生命周期和运作过程有非常重要的作用。

首先看看compile

compile意思是我们希望指令和数据在放入DOM前进行对DOM的操作,因为我们从上一节生命周期中知道,在没有链接之前,我们的DOM虽然生成了,但是我们可以修改DOM,例如添加或者删除节点。但是我们并不推荐这样做。

compile指令可以返回一个对象或者函数,这里我们可以利用Angular的DOM操作element来实现

1
2
3
4
5
6
7
8
9
10
11
angular.module('myApp',[])
.directive('myDirective',function(){
return{
compile:function(tEle,tAttrs,transcludeFn){
//这里进行DOM的操作
return function(scope,ele,attrs){
//在这里进行链接函数的链接
}
}
}
});
  • 注意我们只要使用了compile选项就会默认互斥link选项 这样link选项的所有都会被重写。
  • 而且我在compile选项中的函数用了tEle 和tAtts 这是因为这里操作的还没有实例化的template,这里还没有进行链接,所以没有scope和实例。

link指的是我们在compile执行完之后将作用域和DOM进行链接,一般我们指的link都是Postlink,因为如果你直接写Link指令就是默认变成postlink。

postlink的用法

postlink是我们最经常用的一个选项,当然我们默认只会写上link,就是指postlink,他可以为我们链接作用域和编写业务和逻辑代码。

prelink的用法

prelink应该是我们理解Angular中最难的一个选项,prelink会在Postlink之前执行,他在compile之后执行。在prelink中写的是我们在compile DOM操作之后但是又是在postlink执行的业务代码。

实例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
angular.module('myApp',[])
.directive('myDirective',function(){
return{
compile:function(tEle,tAttrs,transcludeFn){
//这里进行DOM的操作
return {
pre: function(scope, iElem, iAttrs){
console.log(name + ': pre link => ' + iElem.html());
},
post: function(scope, iElem, iAttrs){
console.log(name + ': post link => ' + iElem.html());
}
}
}
}
});

compile pre-link post-link 执行的顺序

首先应该是compile和pre-link是以此的执行的,他们执行完之后post-link才会执行。如果有多个嵌套的指令元素,那么compile 和pre-link会依次执行,而它们执行完后postlink才会执行。
且,pre-link会反向执行,从内到外的执行来保证执行顺序。

这里引用jingxian的文章

传入compile pre-link post-link 的参数解析

当compile 运行的时候,我们的DOM还会修改所以这里的参数都是模板 例如tEle tAttrs
当进入链接时pre-link传入的参数这时是还是tEle tAttrs 因为这里还是链接之前
当然pros-link传入的作用域和元素都是实例我们用,iElement iAttrs scope 如果存在require引入新的作用域的话,我们会多一个controller的参数指向引入的控制器。