My Little World

learn and share


  • 首页

  • 分类

  • 标签

  • 归档

  • 关于
My Little World

Ajax和跨域

发表于 2017-08-19

ajax

ajax全称Asynchronous JavaScript and XML(异步的JavaScript与XML),是网页无需刷新页面、使用js与服务器进行交互的一种技术。

ajax的基本流程可以概括为:页面上js脚本实例化一个XMLHttpRequest对象,设置好服务器端的url、必要的查询参数、回调函数之后,向服务器发出请求,
服务器在处理请求之后将处理结果返回给页面,触发事先绑定的回调函数。
这样,页面脚本如果想要改变一个区域的内容,只需要通过ajax向服务器获取与该区域有关的少量数据,在回调函数中将该区域的内容替换掉即可,不需要刷新整个页面。

XMLHttpRequest在发送请求的时候,有两种方式:同步与异步。
同步方式是请求发出后,一直到收到服务器返回的数据为止,浏览器进程被阻塞,页面上什么事也做不了。
而异步方式则不会阻塞浏览器进程,在服务端返回数据并触发回调函数之前,用户依然可以在该页面上进行其他操作。
ajax的核心是异步方式,而同步方式只有在极其特殊的情况下才会被用到。

XMLHttpRequest 对象是一个接口,用于创建一个http请求对象实例,打开一个URL,然后发送这个请求,
当传输完毕后,结果的HTTP状态以及返回的响应内容也可以从请求对象中获取
五种状态
0:未打开
1:未发送
2:以获取响应头
3:正在下载响应体
4:请求完成
XMLHttpRequest API

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
const xhr = new XMLHttpRequest()

xhr.onreadystatechange = function () {
switch (xhr.readyState) {
case 0:
// UNSENT (未打开)
debugger
break
case 1:
// OPENED (未发送)
debugger
break
case 2:
// HEADERS_RECEIVED (已获取响应头)
debugger
break
case 3:
// LOADING (正在下载响应体)
debugger
break
case 4:
// DONE (请求完成)
if (xhr.status === 200) {
console.log(xhr.responseType)
console.log(xhr.responseText)
console.log(xhr.response)
}
break
}
}

xhr.open('GET', 'http://y.com:7001/json', true)
xhr.send(null)

使用ajax 封装POST,GET请求

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
function Ajax(method,url,callback,data,async){
method = method.toUpperCase()
async = async || true
let xmlHttp = null;
if (XMLHttpRequest) {
xmlHttp = new XMLHttpRequest();//服务器请求对象
}
else {
xmlHttp = new ActiveXObject('Microsoft.XMLHTTP');//兼容微软请求对象
}
let params = [];
for (var key in data){
params.push(key + '=' + opt.data[key]);
}
params = params.join('&');
if (method === 'POST') {//请求方法为POST,则执行如下操作
xmlHttp.open(method, url, async);
xmlHttp.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded;charset=utf-8');
xmlHttp.send(params);
}
else if (method === 'GET') {//请求方法为GET,则执行如下操作
xmlHttp.open(opt.method, opt.url + '?' + params, async);
xmlHttp.send(null);
}
xmlHttp.onreadystatechange = function () {
if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {//响应是否成功
var data = JSON.parse(xmlHttp.responseText);
callback(data);
}else{
throw xmlHttp.responseText
}
};
}

同源策略

为了保证用户信息的安全,防止恶意的网站窃取数据
解决办法:同源策略
同源策略是指三个相同:协议相同,域名相同,端口相同
以上三个不相同则是非同源,非同源之间相互访问即跨域访问

跨域和ip没有关系

跨域

浏览器在阻止跨域,阻止方式可能是在一开始就限制了发起跨站的请求,也可能是跨站请求可以正常发起,但返回结果被浏览器拦截了

为什么要防止跨域

跨域访问时会受到同源策略的三个限制
1、Cookie、LocalStorage 和 IndexDB 无法读取。
通过浏览器document.cookie我们可以获取用户登录态,如果cookie可以读取的话,
就会出现在A公司网站里可以去B公司网站获取登录信息的事情,这样就容易将用户信息泄露
2、DOM 无法获得
如果DOM可以获得,现在我是一个假网站,利用iframe套嵌一个目前线上运营的电商网站,那么
消费者在输入支付密码时,那我就可以获取input的值,从而获取用户支付密码
3、AJAX 请求不能发送
如果AJAx可以发送的话,那我们就能将内网东西下载下来发送到外网服务器,从而造成内网信息泄露

如何实现跨域

jsonp

原理是利用<script>标签可以在任何域下获取资源的原理,将要跨域获取的接口url放在<script>标签的src里面,
然后js将标签放到body里面,其中url包含一个callback参数,用于指向处理response的函数,这个函数我们挂载的window上,
即我们在js中定义的的函数

如果在浏览器直接访问接口’http://x.com:7001/json?callback=xxx'
页面会显示

1
/**/ typeof xxx === 'function' && xxx({"msg":"hello world"});

就是说,在请求这个接口时,会去window上找xxx这个对象,看它是不是函数,如果是函数,
就将接口定义的response({“msg”:”hello world”})作为参数传递给xxx函数,并执行xxx函数

现在在http://y.com/x.html页面中进行跨域

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
//json 接口  服务端

module.exports = app => {
class MsgController extends app.Controller {
* index(req) {
this.ctx.body = { msg: 'hello world' }
}
}
return MsgController
}

// js 客户端请求
//方法一:

//定义相应处理函数
window.xxx = function (value) {
console.log(value)
}
//添加script标签
var script = document.createElement('script')
script.src = 'http://x.com:7001/json?callback=xxx'
document.body.appendChild(script)

//方法二:
require(['http://x.com:7001/json?callback=define'], function (value) {
console.log(value)
})

现在访问http://y.com/x.html,在浏览器console就会打印{"msg":"hello world”}

CORS

XMLHttpRequest 2.0以后可以使用cors方法进行跨域
CORS需要浏览器和服务器同时支持
整个CORS通信过程,都是浏览器自动完成,不需要用户参与。
对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。
浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。
实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//cors接口 服务端
module.exports = app => {
class CrosController extends app.Controller {
* index(req) {
this.ctx.set('Access-Control-Allow-Origin', '*')//如果不添加则会禁止访问
// this.ctx.set('Access-Control-Allow-Origin', 'http://xx.com')
// 如果我们要 http://*.qq.com 都支持跨域怎么办?
this.ctx.body = { msg: 'hello world' }
}
}
return CrosController
}

//js应用 客户端
var xhr = new XMLHttpRequest()
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
console.log(JSON.parse(xhr.responseText).msg)
}
}
xhr.withCredentials = true//在头部添加cookie带到y.stuq,如果不设置,则不带
xhr.open('GET', 'http://x.com:7001/cros')
xhr.send(null)

跨域资源共享 CORS 详解
HTTP访问控制(CORS)

Access-Control-Allow-Origin 的属性值只允许设置为单个确定域名字符串或者 (),设置的话,最不安全,允许所有域可以访问

在服务器端设置CORS跨域请求中的多域名白名单,可以实现Access-Control-Allow-Origin 允许对某一个或几个网站开放跨域请求权限

原理就是在服务器端判断请求的Header中Origin属性值(req.header.origin)是否在我们的域名白名单列表内。
如果在白名单列表内,那么我们就把 Access-Control-Allow-Origin 设置成当前的Origin值,这样就满足了Access-Control-Allow-Origin 的单一域名要求,也能确保当前请求通过访问;如果不在白名单列表内,则返回错误信息。

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
// 判断origin是否在域名白名单列表中
function isOriginAllowed(origin, allowedOrigin) {
if (_.isArray(allowedOrigin)) {
for(let i = 0; i < allowedOrigin.length; i++) {
if(isOriginAllowed(origin, allowedOrigin[i])) {
return true;
}
}
return false;
} else if (_.isString(allowedOrigin)) {
return origin === allowedOrigin;
} else if (allowedOrigin instanceof RegExp) {
return allowedOrigin.test(origin);
} else {
return !!allowedOrigin;
}
}


const ALLOW_ORIGIN = [ // 域名白名单
'*.233.666.com',
'hello.world.com',
'hello..*.com'
];

app.post('a/b', function (req, res, next) {
let reqOrigin = req.headers.origin; // request响应头的origin属性

// 判断请求是否在域名白名单内
if(isOriginAllowed(reqOrigin, ALLOW_ORIGIN)) {
// 设置CORS为请求的Origin值
res.header("Access-Control-Allow-Origin", reqOrigin);
res.header('Access-Control-Allow-Credentials', 'true');

// 业务代码逻辑代码 ...
// ...
} else {
res.send({ code: -2, msg: '非法请求' });
}
});

与JSONP的比较:
CORS与JSONP的使用目的相同,但是比JSONP更强大。
JSONP只支持GET请求,CORS支持所有类型的HTTP请求。JSONP的优势在于支持老式浏览器,以及可以向不支持CORS的网站请求数据。

iframe-Hash

Location 对象是 Window 对象的一个部分,可通过 window.location 属性来访问
hash是location对象的的一个属性,可以设置或返回从井号 (#) 开始的 URL(锚)

iframe是HTML标签,作用是文档中的文档,或者浮动的框架(FRAME)。iframe元素会创建包含另外一个文档的内联框架(即行内框架)。

原理是在原域页面包装 跨域src的iframe标签,在跨域src的文件里请求跨域的资源(此时二者同域),
跨域src文件是可以获取到iframe父类,即我们原域的window对象,
通过改变原域的hash值,引发原域onhashchange,从而将资源带回到原域

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
//原域js
//包装iframe
var iframe = document.createElement('iframe')
iframe.src = 'http://x.com:7001/public/hash.html'
document.body.appendChild(iframe)
//处理请求资源
window.onhashchange = function () {
// 小练习,做个工具方法,取出query的值
console.log(location.hash)
}

//跨域的hash.html文件

<!DOCTYPE html>
<html>
<head>
</head>
<body>
<script>
var xhr = new XMLHttpRequest()
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
var res = JSON.parse(xhr.responseText)
parent.location.href = `http://y.stuq.com:7001/public/3.html#msg=${res.msg}` //引起原域onhashchange,同时将response带回
}
}
xhr.open('GET', 'http://x.com:7001/json', true) //请求同域资源
xhr.send(null)
</script>
</body>
</html>

iframe-window.name

原理是利用iframe的window.name,name 值在不同的页面(甚至不同域名)加载后依旧存在(如果没修改则值不会变化),并且可以支持非常长的 name 值(2MB)

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
//原域js
var iframe = document.createElement('iframe')
iframe.src = 'http://x.com:7001/public/name.html'
document.body.appendChild(iframe)

var times = 0
iframe.onload = function () {
if (++times === 2) {//第一次打开跨域页面name,第二次加载通知原域iframe改变值
console.log(JSON.parse(iframe.contentWindow.name))
}
}

//name.html
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<script>
var xhr = new XMLHttpRequest()
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
window.name = xhr.responseText //这里是原域iframe的window,iframe不能跨域读取对象,所以
location.href = 'http://y.com:7001/public/index.html' //再次加载iframe,通知原域parent,iframe的contentWindow.name需要做更改
//href的值,依然是跨域的也可以,这里是加载回原域的一个文件
}
}
xhr.open('GET', 'http://x.com:7001/json', true)
xhr.send(null)
</script>
</body>
</html>

iframe-postMessage

利用HTML5的postMessage方法

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
//原域js
var iframe = document.createElement('iframe')
iframe.src = 'http://x.stuq.com:7001/public/post.html'
document.body.appendChild(iframe)

window.addEventListener('message', function(e) {
console.log(JSON.parse(e.data))
}, false);

//post.html
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<script>
var xhr = new XMLHttpRequest()
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
parent.postMessage(xhr.responseText, '*') //拿到父级页面parent,执行postmessage的一个操作,从而引发父级页面的message事件
//*代表targetOrigin可以是任何域
}
}
xhr.open('GET', 'http://x.stuq.com:7001/json', true)
xhr.send(null)
</script>
</body>
</html>

Window.postMessage() API

小结

跨域方法很多
选择如何使用可以考虑以下几方面
1.场景,选择简单的
2.安全,解决问题是否足够安全
3.数据来源,如果跨域接口可以传资源给原域,则可以使用iframe代理
4.承接第三种情景,如果接口不允许传资源,则只能寄希望于后台,使用反向代理的方法获取

My Little World

Karma test

发表于 2017-08-12

Karma test

Karma是由Google团队开发的一套前端测试运行框架。它不同于测试框架(例如jasmine,mocha等),运行在这些测试框架之上。主要完成以下工作:
Karma启动一个web服务器,生成包含js源代码和js测试脚本的页面;
运行浏览器加载页面,并显示测试的结果;
如果开启检测,则当文件有修改时,执行继续执行以上过程。

搭建测试环境

npm install -g karma-cli //让全局都可以运行karma的命令行,命令行工具
npm i karma –save-dev //只在当前项目中使用karma
npm install //安装项目的依赖 package.json

1
2
3
4
5
6
7
"devDependencies": {
"karma": "^1.7.0",
"karma-chrome-launcher": "^2.2.0",
"karma-mocha": "^1.3.0",
"mocha": "^3.5.0",
"should": "^11.2.1"
}

karma init //在cmd中运行该命令,构建karma.conf.js文件,运行后会询问相关问题然后生成karma.conf.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
33
34
Which testing framework do you want to use ?
Press tab to list possible options. Enter to move to the next question.
> mocha //选择要使用的框架,不能直接输入,是选择题,点击键盘箭头可更改选项

Do you want to use Require.js ?
This will add Require.js plugin.
Press tab to list possible options. Enter to move to the next question.
> no //是否add Require.js plugin

Do you want to capture any browsers automatically ?
Press tab to list possible options. Enter empty string to move to the next quest
ion.
> Chrome //选择用哪个浏览器打开测试页,也是选择题,不能输入
>

What is the location of your source and test files ?
You can use glob patterns, eg. "js/*.js" or "test/**/*Spec.js".
Enter empty string to move to the next question.
> src/*.js //添加要测试的代码的路径,不是测试用例代码的路径,同时,代码中用到的其他文件也在这里同时添加,
11 08 2017 15:03:13.822:WARN [init]: There is no file matching this pattern.

> // 回车可以切换下一行继续添加,连续回车进入下一个问题

Should any of the files included by the previous patterns be excluded ?
You can use glob patterns, eg. "**/*.swp".
Enter empty string to move to the next question.
>

Do you want Karma to watch all the files and run the tests on change ?
Press tab to list possible options.
> yes //是否跟所有文件自动开启测试


Config file generated at "G:\css\homework1\karma.conf.js".

karma.conf.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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
module.exports = function(config) {
config.set({

// base path that will be used to resolve all patterns (eg. files, exclude)
basePath: '',//根路径,后面配置的基本所有相对路径都会根据这个路径来构造。


// frameworks to use 使用到的框架
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
frameworks: ['mocha'],


// list of files / patterns to load in the browser 将会在浏览器里面执行的代码
files: [
'node_modules/should/should.js',
'js/*.js',
'quz/*.js',
'test/*.js'
],


// list of files to exclude 需要从 files 中排除掉的文件
exclude: [
],


// preprocess matching files before serving them to the browser需要做预处理的文件,以及这些文件对应的预处理器。
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
preprocessors: {////此处就可以将 coffee 、 ES6 等代码转换一下。
'js/*.js': ['coverage'],//测试覆盖率
'quz/*.js': ['coverage']//测试覆盖率
},


// test results reporter to use 测试结果报告器
// possible values: 'dots', 'progress'
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
reporters: ['progress', 'coverage'],

// 覆盖率报告器配置
coverageReporter: {
type : 'lcov', //html格式会生成html文件,lcov格式可以和coveralls结合生成coveralls徽章
dir : 'coverage/'
},

// web server port 服务器端口号
port: 9876,


// enable / disable colors in the output (reporters and logs)
colors: true,


// level of logging 日志级别
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
logLevel: config.LOG_INFO,


// enable / disable watching file and executing tests whenever any file changes 启用/禁用监视文件变化重新执行测试的功能
autoWatch: true,


// start these browsers 使用的浏览器
// available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
browsers: ['Chrome'],


// Continuous Integration mode true测试一次就结束,false测试完一直处于测试状态
// if true, Karma captures browsers, runs the tests and exits
singleRun: true,

// Concurrency level
// how many browser should be started simultaneous
concurrency: Infinity
})
}

集成测试Travis CI

Travish CI使用
通过Travis CI测试我们可以获得一个测试通过的标志Build Status,可以将它放在github仓库readme.md文件里面
coverage1
更改链接中branch的值可以获得对应分支的测试结果

测试代码覆盖率 Coverage

衡量测试脚本的质量–代码覆盖率:测试中运行到的代码占所有代码的比率

安装测试覆盖率工具
npm i –save-dev karma-coverage

修改配置文件karma.conf.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// modified
preprocessors: {
'src/**/*.js': ['coverage'], //要测试的功能代码路径:['coverage'] 多个文件夹就逗号隔开,按格式写下去
'js/*.js': ['coverage'],
'quz/*.js': ['coverage']
},

//modified
reporters: ['progress', 'coverage'],

// add
coverageReporter: {
type : 'html',
dir : 'coverage/'
},

运行karma start,会新增coverage文件夹,里面只有一个浏览器名的文件夹,里面是根据preprocessors添加的路径生成对应的文件夹和其他文件,
其他文件里面有index.html文件,是整个测试的测试报告,文件夹里是每个文件的覆盖测试结果和代码具体的覆盖情况

注意:如果不想将测试结果上传github,记得更改.gitignore文件,将coverage文件夹忽略掉

获取覆盖率标志

github 仓库经常会看到这个标志
coverage2
就是说项目的测试覆盖率是91%,要获取这个标志我们需要将测试覆盖率放到Coveralls上

安装 coveralls方便我们在Travis CI上测试完之后将结果上传
npm i coveralls –save-dev

更改karma.conf.js
coverageReporter: {
type : ‘lcov’, //将html改为lcov
dir : ‘coverage/‘
},
接下来操作步骤可以有两种
方法一:
仅更改 package.json文件,不保留测试命令karma start
更改package.json的scripts

1
2
3
"scripts": {
"test": "./node_modules/karma/bin/karma start --browsers Firefox --single-run && find coverage -name lcov.info -print0 | xargs -0 cat | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage"
}

方法二:
更改package.json文件,保留测试命令karma start

1
2
3
4
"scripts": {
"test": "karma start",
"report": "find coverage -name lcov.info -print0 | xargs -0 cat | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage"
}

也要更改.travis.yml文件,添加以下语句

1
2
after_success:
- "npm run report"

然后push代码,等Travi CI 完成测试后,我们可以在Coveralls上获取标志,
在总的测试结果那个页面不是具体某次提交的页面获取
coverage3
点击EMBED可以获取不同格式的标志代码,
复制粘贴MARKDOWN 格式可以放在readme.md文件中直接使用

相关链接:
使用 Karma 在真实浏览器上测试
前端单元测试之Karma环境搭建

My Little World

Karma test

发表于 2017-08-12

Karma test

Karma是由Google团队开发的一套前端测试运行框架。它不同于测试框架(例如jasmine,mocha等),运行在这些测试框架之上。主要完成以下工作:
Karma启动一个web服务器,生成包含js源代码和js测试脚本的页面;
运行浏览器加载页面,并显示测试的结果;
如果开启检测,则当文件有修改时,执行继续执行以上过程。

搭建测试环境

npm install -g karma-cli //让全局都可以运行karma的命令行,命令行工具
npm i karma –save-dev //只在当前项目中使用karma
npm install //安装项目的依赖 package.json

1
2
3
4
5
6
7
"devDependencies": {
"karma": "^1.7.0",
"karma-chrome-launcher": "^2.2.0",
"karma-mocha": "^1.3.0",
"mocha": "^3.5.0",
"should": "^11.2.1"
}

karma init //在cmd中运行该命令,构建karma.conf.js文件,运行后会询问相关问题然后生成karma.conf.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
33
34
Which testing framework do you want to use ?
Press tab to list possible options. Enter to move to the next question.
> mocha //选择要使用的框架,不能直接输入,是选择题,点击键盘箭头可更改选项

Do you want to use Require.js ?
This will add Require.js plugin.
Press tab to list possible options. Enter to move to the next question.
> no //是否add Require.js plugin

Do you want to capture any browsers automatically ?
Press tab to list possible options. Enter empty string to move to the next quest
ion.
> Chrome //选择用哪个浏览器打开测试页,也是选择题,不能输入
>

What is the location of your source and test files ?
You can use glob patterns, eg. "js/*.js" or "test/**/*Spec.js".
Enter empty string to move to the next question.
> src/*.js //添加要测试的代码的路径,不是测试用例代码的路径,同时,代码中用到的其他文件也在这里同时添加,
11 08 2017 15:03:13.822:WARN [init]: There is no file matching this pattern.

> // 回车可以切换下一行继续添加,连续回车进入下一个问题

Should any of the files included by the previous patterns be excluded ?
You can use glob patterns, eg. "**/*.swp".
Enter empty string to move to the next question.
>

Do you want Karma to watch all the files and run the tests on change ?
Press tab to list possible options.
> yes //是否跟所有文件自动开启测试


Config file generated at "G:\css\homework1\karma.conf.js".

karma.conf.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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
module.exports = function(config) {
config.set({

// base path that will be used to resolve all patterns (eg. files, exclude)
basePath: '',//根路径,后面配置的基本所有相对路径都会根据这个路径来构造。


// frameworks to use 使用到的框架
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
frameworks: ['mocha'],


// list of files / patterns to load in the browser 将会在浏览器里面执行的代码
files: [
'node_modules/should/should.js',
'js/*.js',
'quz/*.js',
'test/*.js'
],


// list of files to exclude 需要从 files 中排除掉的文件
exclude: [
],


// preprocess matching files before serving them to the browser需要做预处理的文件,以及这些文件对应的预处理器。
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
preprocessors: {////此处就可以将 coffee 、 ES6 等代码转换一下。
'js/*.js': ['coverage'],//测试覆盖率
'quz/*.js': ['coverage']//测试覆盖率
},


// test results reporter to use 测试结果报告器
// possible values: 'dots', 'progress'
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
reporters: ['progress', 'coverage'],

// 覆盖率报告器配置
coverageReporter: {
type : 'lcov', //html格式会生成html文件,lcov格式可以和coveralls结合生成coveralls徽章
dir : 'coverage/'
},

// web server port 服务器端口号
port: 9876,


// enable / disable colors in the output (reporters and logs)
colors: true,


// level of logging 日志级别
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
logLevel: config.LOG_INFO,


// enable / disable watching file and executing tests whenever any file changes 启用/禁用监视文件变化重新执行测试的功能
autoWatch: true,


// start these browsers 使用的浏览器
// available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
browsers: ['Chrome'],


// Continuous Integration mode true测试一次就结束,false测试完一直处于测试状态
// if true, Karma captures browsers, runs the tests and exits
singleRun: true,

// Concurrency level
// how many browser should be started simultaneous
concurrency: Infinity
})
}

集成测试Travis CI

Travish CI使用
通过Travis CI测试我们可以获得一个测试通过的标志Build Status,可以将它放在github仓库readme.md文件里面
coverage1
更改链接中branch的值可以获得对应分支的测试结果

测试代码覆盖率 Coverage

衡量测试脚本的质量–代码覆盖率:测试中运行到的代码占所有代码的比率

安装测试覆盖率工具
npm i –save-dev karma-coverage

修改配置文件karma.conf.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// modified
preprocessors: {
'src/**/*.js': ['coverage'], //要测试的功能代码路径:['coverage'] 多个文件夹就逗号隔开,按格式写下去
'js/*.js': ['coverage'],
'quz/*.js': ['coverage']
},

//modified
reporters: ['progress', 'coverage'],

// add
coverageReporter: {
type : 'html',
dir : 'coverage/'
},

运行karma start,会新增coverage文件夹,里面只有一个浏览器名的文件夹,里面是根据preprocessors添加的路径生成对应的文件夹和其他文件,
其他文件里面有index.html文件,是整个测试的测试报告,文件夹里是每个文件的覆盖测试结果和代码具体的覆盖情况

注意:如果不想将测试结果上传github,记得更改.gitignore文件,将coverage文件夹忽略掉

获取覆盖率标志

github 仓库经常会看到这个标志
coverage2
就是说项目的测试覆盖率是91%,要获取这个标志我们需要将测试覆盖率放到Coveralls上

安装 coveralls方便我们在Travis CI上测试完之后将结果上传
npm i coveralls –save-dev

更改karma.conf.js
coverageReporter: {
type : ‘lcov’, //将html改为lcov
dir : ‘coverage/‘
},
接下来操作步骤可以有两种
方法一:
仅更改 package.json文件,不保留测试命令karma start
更改package.json的scripts

1
2
3
"scripts": {
"test": "./node_modules/karma/bin/karma start --browsers Firefox --single-run && find coverage -name lcov.info -print0 | xargs -0 cat | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage"
}

方法二:
更改package.json文件,保留测试命令karma start

1
2
3
4
"scripts": {
"test": "karma start",
"report": "find coverage -name lcov.info -print0 | xargs -0 cat | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage"
}

也要更改.travis.yml文件,添加以下语句

1
2
after_success:
- "npm run report"

然后push代码,等Travi CI 完成测试后,我们可以在Coveralls上获取标志,
在总的测试结果那个页面不是具体某次提交的页面获取
coverage3
点击EMBED可以获取不同格式的标志代码,
复制粘贴MARKDOWN 格式可以放在readme.md文件中直接使用

相关链接:
使用 Karma 在真实浏览器上测试
前端单元测试之Karma环境搭建
前端自动化测试解决方案探析

My Little World

断言语句assert

发表于 2017-08-12

测试用例里面的一种判断语句

在mocha中需要require
var assert = require(‘assert’)

参数一般有三个
value 待判断的值
expected 要和value进行比较的值,看二者是否相等或者不相等,如果没有expected,则是在判断value的值是否为真
message 判断结果为错误时,抛出来的错误提醒语句,如果 message 参数为 undefined,则赋予默认的错误信息。

assert(value[, message]) 同assert.ok()
assert.ok(value[, message]) value如果不为真值,抛出一个带有 message 属性的 AssertionError

assert.fail(message) 抛出错误信息message

assert.fail(actual, expected, message, operator) 根据参数抛出相应错误信息
如果 message 不存在,则错误信息会被设为 actual 的值加分隔符 operator 再加 expected 的值。 否则,错误信息为 message 的值,operator没有的话,默认为!=

assert.ifError(value) 如果 value 为真,则抛出 value,如果为假,则测试通过

assert.equal(actual, expected[, message]) 使用相等运算符(==)测试 actual 参数与 expected 参数是否相等。
assert.deepEqual(actual, expected[, message]) 测试 actual 参数与 expected 参数是否深度相等。 原始值使用 相等运算符(==)比较,只比较可枚举的自身属性。
深度相等意味着子对象的可枚举的自身属性也会被比较

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const obj1 = {
a: {
b: 1
}
};
const obj3 = {
a: {
b: 1
}
};
const obj4 = Object.create(obj1);
assert.deepEqual(obj1, obj4);
// 抛出 AssertionError: { a: { b: 1 } } deepEqual {}
// 原型会被忽略
assert.deepEqual(obj1, obj3);
// 通过,两个对象相等

assert.deepStrictEqual(actual, expected[, message])
大多数情况下与 assert.deepEqual() 一样,但有三个例外:

原始值使用 全等运算符(===)比较。使用SameValueZero比较法来比较设置的值及映射的键(也就意味不用考虑caveats)。
对象的 原型 也使用 全等运算符 比较。
对象的类型标签应该相同。

assert.notEqual(actual, expected[, message]) 使用 不等运算符(!=)测试是否不相等。
assert.notDeepEqual(actual, expected[, message]) 测试是否不深度相等。 与 assert.deepEqual() 相反。
assert.notStrictEqual(actual, expected[, message]) 使用 不全等运算符(!==)测试是否不全等。
assert.notDeepStrictEqual(actual, expected[, message]) 测试是否不深度全等。 与 assert.deepStrictEqual() 相反。

assert.throws(block[, error][, message]) 期望 block 函数抛出错误error,如果block(function)抛出error的错误,则测试通过
error 可以是构造函数、正则表达式、或自定义的验证函数。

assert.doesNotThrow(block[, error][, message]) 断言 block 函数不会抛出错误
当 assert.doesNotThrow() 被调用时,它会立即调用 block 函数。
如果抛出错误且错误类型与 error 参数指定的相同,则抛出 AssertionError。 如果错误类型不相同,或 error 参数是 undefined,则错误会被抛回给调用者。
如果block不抛出错误,则测试通过

My Little World

变量提升

发表于 2017-08-09

一个变量的生成需要经历 创建、初始化、赋值三个阶段
let 的「创建」过程被提升了,但是初始化没有提升。
var 的「创建」和「初始化」都被提升了。
function 的「创建」「初始化」和「赋值」都被提升了。
const,其实 const 和 let 只有一个区别,那就是 const 只有「创建」和「初始化」,没有「赋值」过程。
let
1、
假设有如下代码:

function fn(){
var x = 1
var y = 2
}
fn()

在执行 fn 时,会有以下过程(不完全):

进入 fn,为 fn 创建一个环境。
找到 fn 中所有用 var 声明的变量,在这个环境中「创建」这些变量(即 x 和 y)。
将这些变量「初始化」为 undefined。
开始执行代码
x = 1 将 x 变量「赋值」为 1
y = 2 将 y 变量「赋值」为 2
也就是说 var 声明会在代码执行之前就将「创建变量,并将其初始化为 undefined」。

这就解释了为什么在 var x = 1 之前 console.log(x) 会得到 undefined。

2、
fn2()

function fn2(){
console.log(2)
}
JS 引擎会有一下过程:

找到所有用 function 声明的变量,在环境中「创建」这些变量。
将这些变量「初始化」并「赋值」为 function(){ console.log(2) }。
开始执行代码 fn2()
也就是说 function 声明会在代码执行之前就「创建、初始化并赋值」
3、
{
let x = 1
x = 2
}
只看 {} 里面的过程:

找到所有用 let 声明的变量,在环境中「创建」这些变量
开始执行代码(注意现在还没有初始化)
执行 x = 1,将 x 「初始化」为 1(这并不是一次赋值,如果代码是 let x,就将 x 初始化为 undefined)
执行 x = 2,对 x 进行「赋值」
这就解释了为什么在 let x 之前使用 x 会报错:

let x = ‘global’
{
console.log(x) // Uncaught ReferenceError: x is not defined
let x = 1
}
原因有两个

console.log(x) 中的 x 指的是下面的 x,而不是全局的 x
执行 log 时 x 还没「初始化」,所以不能使用(也就是所谓的暂时死区)

补充
1.
在进入一个执行环境后,先把 var 和 function 声明的变量前置, 再去顺序执行代码
是 var 声明在前还是 function 声明的在前?按先来后到,同名覆盖。当然如果一个变量已经有值,再 var 是无效的

1
2
3
4
5
6
7
8
9
10

var fn
function fn(){}

console.log(fn) //function

function fn(){}
var fn //已经声明过 fn, 再 var 无效,并不会重置为 undefined

console.log(fn) //function

2.
函数在执行的过程中,先从自己内部找变量
如果找不到,再从创建当前函数所在的作用域去找, 以此往上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var a = 1
function fn1(){
function fn2(){
console.log(a)
}
var a = 2
return fn2
}
var fn = fn1()
fn() //输出多少 2
////////////////////////////////
var a = 1
function fn1(){
var a = 2
return fn2
}
function fn2(){
console.log(a)
}
var fn = fn1()
fn() //输出多少 1

相关

1
2
3
4
5
var a = {n:1};
var b = a;
a.x = a = {n:2};
console.log(a)//{n:2}
console.log(b)//{n:1,x:{n:2}}

a.x = a = {n:2};执行顺序

  1. a.x,这时a,b指向{n:1},增加x,属性,但值为undefined,即这时a,b为{n:1,x:undefined}

2.a={n:2},a这时指向{n:2},b依然为{n:1,x:undefined}

3.a.x = a;这时的x是{n:1,x:undefined}中的x,指向a,此时a指向{n:2}

1
a==1 && a==2 && a==3

可能为true
关键在于==在比较两端之前会调用对象的valueof,toString等方法,只要a对象中的toString方法有递增值返回就可以实现

1
2
3
4
5
6
const a = {
i: 1,
toString: function () {
return a.i++;
}
}

立即执行函数的目的
造出一个函数作用域,防止污染全局变量
ES 6 新语法

1
2
3
{
let name
}

My Little World

关于this

发表于 2017-08-09

普通函数中的this

  1. this总是代表它的直接调用者, 例如 obj.func ,那么func中的this就是obj
    2.在默认情况(非严格模式下,未使用 ‘use strict’),没找到直接调用者,则this指的是 window
    3.在严格模式下,没有直接调用者的函数中的this是 undefined
    4.使用call,apply,bind绑定的,this指的是 绑定的对象,
    bind绑定一次后不会改变,且bind 的执行的结果返回的是绑定了一个对象的新函数
    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
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    1、
    function test() {
    console.log(this);//window
    }
    test();

    2、
    function test() {
    'use strict';
    console.log(this);//undefined
    }
    test();

    3、
    window.val = 1;
    var obj = {
    val: 2,
    fn: function () {
    this.val *= 2; //普通函数,this指向调用者
    val *= 2;
    console.log(this.val);
    console.log(val);
    }
    };
    obj.fn();// 4 2
    var func = obj.fn;
    func(); //8 8
    //obj.fn()执行时,val 没有在fn的作用域里面定义,则去obj.fn()的作用域里面找,obj.fn()位于window,window.val是1;this指向obj,this.val是2
    //func()执行时,window.val由于执行obj.fn(),现在是2;func()在window作用域下执行,this就是window,所以this.val和val都是window.val

    4、
    function f() {
    var test = 'in the f!';
    setTimeout(function(){ //是函数就会建立作用域
    console('inner '+ test) // inner in the f!
    }, 0);
    }
    //以上代码等于
    function f() {
    var test = 'in the f!';

    function ff() {
    alert('inner ' + test) //test在ff里面没定义,但在f里面进行了定义
    } // 能访问到f中的test局部变量

    setTimeout(ff, 0); // inner in the f!
    }

    f();
    5、
    var lzh = {
    name: 'lzh',
    say: function(something){
    alert(something + this.name);
    }
    }

    var iny = {
    name: 'iny'
    }

    lzh.say.apply(iny, ['hi, I am ']); // 输出 hi I am iny
    6、
    var arr = []
    for(var i=0; i<3; i++){
    arr[i] = function(){ console.log(this) }
    }
    var fn = arr[0]

    arr[0]
    /*
    解析: 因为函数是个特殊的对象,所以 arr 相当于 { '0': function(){}, '1': function(){}, '2': function(){}, length:3}
    arr[0]相当于 `arr['0']` 相当于 `arr.0` (当然这种写法不符合规范),所以 arr[0]等价于 arr.0.call(arr), this就是 arr
    */

    fn()
    /*
    解析: 相当于 `fn.call(undefined)`, 所以 fn 里面的 this 是 Window
    */
    7、
    var app = {
    container: document.querySelector('body'),
    bind: function(){
    this.container.addEventListener('click', this.sayHello) //点击的时候会执行 sayHello,sayHello 里面的 this 代表 body 对象
    this.container.addEventListener('click', this.sayHello.bind(this)) //点击的时候会执行 sayHello,sayHello 里面的 this 代表 app 对象
    },
    sayHello: function(){
    console.log(this)
    }
    }
    app.bind()

箭头函数中的this

默认指向在定义它时,它所绑定的对象的上一级,而不是执行时的对象, 定义它的时候,可能环境是window

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function foo() {
return () => {
return () => {
return () => {
console.log(this);
};
};
};
}
foo()()()() //window
var f = foo.call({id: 1});
f()()() //{id:1}
var t1 = f.call({id: 2})()();//{id:1}
var t2 = f().call({id: 3})();//{id:1}
var t3 = f()().call({id: 4});//{id:1}

setTimeout中的this

普通函数指向window
箭头函数指向定义的对象
引用函数,指向调用对象

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
 0、
var obj = {
fn1 : function () {
console.log(this); //obj
},
fn2 : function () {
setTimeout(function () {
console.log(this); //window 匿名函数执行在window环境,找不到宿主对象,所以指向window
},0);
},
fn3: function () {
setTimeout(() => {
console.log(this); //obj 箭头函数创建在obj上
},100);
}
fn4: function () {
var that = this;
setTimeout(function () {
console.log(that) //obj 在setTimeout里面引用obj
console.log(this) //window
});
}
fn5: function () {
var f1 = () => {
console.log(this); // obj f1定义处在obj里面
setTimeout(() => {
console.log(this); // obj 箭头函数定义处在obj里面
})
}
f1();
}
fn6: function () {
var f2 = function () {
console.log(this); // window, f2调用时,没有宿主对象,默认是window
setTimeout(() => {
console.log(this); // window 箭头函数定义在f2确定的window里面
})
};
f2();
}
fn7: function () {
'use strict';
var f3 = function () {
console.log(this); // undefined
setTimeout(() => {
console.log(this); // undefined
})
};
f3();
}
};
obj.fn1();
obj.fn2();
obj.fn3();
obj.fn4();
···

1、
function foo(){
setTimeout(function(){
console.log(this) //window 匿名函数,找不到宿主对象
}, 100);
setTimeout(() => {
console.log(this) //window foo挂载在window对象上
}, 100);
}
foo();
foo.call({id:42}); //window {id:42} foo运行时所在的对象,恰好是箭头函数定义时所在的对象
//call([thisObj[,arg1[, arg2,....]) 方法可将一个函数的对象上下文从初始的上下文改变为由 thisObj 指定的新对象。

2、
function method() {
alert(this.value); // 输出 42 第二个this
}

function Foo() {
this.value = 42;
setTimeout(this.method, 500); // 这里this指向window 第一个this
}

Foo();
//Foo挂载在window上,当执行Foo时,this指向window,Foo里面的value被挂到window,method本来就挂在window上,所以执行this.method就是调用window.method
//method执行,它的this指向window,这时window已经挂上value值42
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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
 //其他理解
let app = {
fn1: function(a){
console.log(this) //app
}
fn2(a) {
consoel.log(this) //app
},
fn3: (a)=>{
console.log(this) //window
}
}

app.fn2.call(app)
app.fn3.call( 它的上一级的 this )
1.
var app = {
init() {
var menu = {
init: ()=>{
console.log(this)
},
bind() {
console.log(this)
}
}
menu.init()
/*相当于 menu.init.call(menu 所在的环境下的 this) , 所以 init 里面的 this 也就是 app。
(假设 app.init 也是箭头函数,想想 menu.init 里面的 this 是什么?)
*/
menu.bind()
/*相当于 menu.bind.call(menu),也就是 menu,所以 bind 里面的 this 就是 menu
*/
}
}
app.init()


2.
var app = {
fn1() {
setTimeout(function(){
console.log(this)
}, 10)
},
fn2() {
setTimeout(()=>{
console.log(this)
},20)
},
fn3() {
setTimeout((function(){
console.log(this)
}).bind(this), 30)
},
fn4: ()=> {
setTimeout(()=>{
console.log(this)
},40)
}
}
app.fn1()
app.fn2()
app.fn3()
app.fn4()

var app = {
fn1() {
function fn(){
console.log(this)
}
//过10ms 后执行
//fn.call(undefined) ,所以输出 Window
},
fn2() {
//过20ms 执行箭头函数
//箭头函数里面没资格有 自己的 this,借用 setTimeout 外面的 this,也就是 app
},
fn3() {
// 创建了一个新函数,这个新函数里面绑定了 外面的this,也就是 app
// 20 ms 后执行新函数,输出 this,也就是刚刚绑定的 app
}
fn4: ()=> {
//过40ms 执行箭头函数
//箭头函数里面没资格有 this,用 setTimeout 外面的 this
//setTimeout 所在的 fn4也是箭头函数,没资格拥有自己的 this,借用外面的 this ,也就是 Window
}

应用

My Little World

Travish CI使用

发表于 2017-08-08

Travish CI使用

1、使用github 账号登陆https://www.travis-ci.org/
2、点击右上角自己的账户名,选择accounts,弹出在github上所有的仓库
ci1.png
点击这个按钮重新从github上获取所有仓库
ci2.png
3、点击对应仓库按钮,变为对勾,则开启对应仓库的集成化测试,每次提交代码到远程仓库都进行一次测试
4、点击仓库名,可进入到测试界面,
ci3.png
5、为项目添加.travis.yml文件
.travis.yml 文件编写的相关语法可参考https://docs.travis-ci.com/user/languages/javascript-with-nodejs/

.travis.yml 文件可以在https://lint.travis-ci.org/页面进行检查,是否编写正确

6、push代码,在Travish CI等待测试结果
页面图标因为有缓存所以可能出现不能及时更换状态的情况,这个注意看结果就好

My Little World

fork仓库

发表于 2017-08-08

fork

在页面点击Fork,将被人的仓库复制到自己的远程仓库
使用git clone 将远程仓库克隆到本地
切换分支到develop git checkout -b develop
修改完代码后
git add . && git commit -m ‘test new pull request’
git push origin develop
然后在github页面找到刚刚fork的仓库,切换branch到develop,
点击New pull request进行merge

保持与原仓库同步

git remote -v
查看当前的远程仓库地址,输出如下:

origin https://github.com/YooHannah/homework2.git (fetch)
origin https://github.com/YooHannah/homework2.git (push)
可以看到从自己帐号 clone 下来的仓库,远程仓库地址是与自己的远程仓库绑定的(这不是废话吗)

接下来运行

git remote add upstream https://github.com/FE-star/homework2.git
这条命令就算添加一个别名为 upstream(上游)的地址,指向之前 fork 的原仓库地址。git remote -v 输出如下:

origin https://github.com/YooHannah/homework2.git (fetch)
origin https://github.com/YooHannah/homework2.git (push)
upstream https://github.com/FE-star/homework2.git (fetch)
upstream https://github.com/FE-star/homework2.git (push)
之后运行下面几条命令,就可以保持本地仓库和上游仓库同步了

git fetch upstream
git checkout master 如果本来就在master上就不用切换
git merge upstream/master
接着就是熟悉的推送本地仓库到远程仓库

git push origin master

Git 合并两个不同的仓库

My Little World

事件冒泡和事件捕获

发表于 2017-08-06

addEventListener/attachEvent/element.onclick

辨别区分addEventListener、attachEvent和element.onclick

attachEvent事件

attachEvent是ie添加事件处理程序,接收两个参数,其中事件类型名称要加”on”,
可以添加多个事件处理程序,按照添加顺序相反的顺序触发

addEventListener事件

addEventListener是给非ie添加事件处理程序,接收三个参数,第一个是事件名,不需要加“on”,
第二个是绑定的函数,第三个参数是一个布尔值,如果是false,就使用传统的冒泡方式,
如果为true,就在捕获阶段调用事件处理程序
可以添加多个事件处理程序,按照添加顺序触发

onclick

el.onclick相当于在标签上写onclick

区别

1.上下文
attachEvent会在全局作用域中运行,this等于window对象
addEventLinstener在其依附的元素的作用域中运行,this等于绑定元素对象
使this关键字都指向元素处理方法

1
2
3
4
5
6
function bind(el, fn, type){
var _fn = function(){
fn.apply(el, arguments);
};
window.addEventListener ? el.addEventListener(type, _fn, false) : el.attachEvent("on" + type, _fn);
}

2.绑定
el.onclick通过标签的onclick属性输入到文档,然后由文档解析成事件
attachEvent和addEventLinstener要在文档解析完成以后,通过文档的dom接口去绑定的事件
资源加载和页面事件

3.取消绑定
el.onclick:el.onclick=null;
addEventListener:removeEventListener();
attachEvent():detachEvent()

4.获取event事件
非IE:

1
2
3
4
5
6
el.onclick=function(event){
  alert(event.type); //"click"
};
el.addEventListener("click",function(event){
  alert(event.type); //"click"
},false);

IE:
通过el.onclick绑定的事件处理程序中,event对象作为window对象的一个属性存在。
el.onclick=function(){
  var event=window.event;
  alert(event.type); //“click”
}
如果通过attachEvent()添加事件处理程序时,event对象作为参数被传入事件处理程序,
el.attachEvent(“onclick”,function(event){
  alert(event.type); //“click”
});

标签时一样

1
<input type="button" value="Click me" onclick="alert(event.type)"/>   //"click"

事件捕获、事件冒泡

二者是指当事件在某一DOM元素被触发时,该DOM元素的父级元素也绑定了触发事件,则触发事件的执行顺序

事件冒泡

事件自下而上依次执行,先执行子元素触发事件,再执行父元素触发事件,
addEventListener第三个参数设置为false,参数不设置默认是冒泡执行

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
<body>
<div id="parent">
  <div id="child" class="child">您好</div>
</div>
</body>

<script type="text/javascript">
document.getElementById("parent").addEventListener("click",function(e){
alert("parent事件被触发,"+this.id);
})
document.getElementById("child").addEventListener("click",function(e){
alert("child事件被触发,"+this.id)
})
document.getElementById("parent").onclick=function(e){
alert("parentonclik事件被触发,"+this.id);
}
document.getElementById("child").onclick=function(e){
alert("childonclik事件被触发,"+this.id);
}
</script>
//点击'您好',弹出顺序:child事件被触发->childonclik事件被触发->parent事件被触发->parentonclik事件被触发
<script type="text/javascript">
document.getElementById("parent").addEventListener("click",function(e){
alert("parent事件被触发,"+this.id);
},false)
document.getElementById("child").addEventListener("click",function(e){
alert("child事件被触发,"+this.id)
},true)
</script>
//点击'您好',先弹出child事件被触发,再弹出parent事件被触发

事件捕获

事件从上到下一次执行,先执行父元素触发事件,再执行子元素触发事件,
addEventListener第三个参数设置为true

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
<body>
<div id="parent">
  <div id="child" class="child">您好</div>
</div>
</body>

<script type="text/javascript">
document.getElementById("parent").addEventListener("click",function(e){
alert("parent事件被触发,"+this.id);
},true)
document.getElementById("child").addEventListener("click",function(e){
alert("child事件被触发,"+this.id)
},true)
document.getElementById("parent").onclick=function(e){
alert("parentonclik事件被触发,"+this.id);
}
document.getElementById("child").onclick=function(e){
alert("childonclik事件被触发,"+this.id);
}
</script>
//点击'您好',弹出顺序:parent事件被触发->child事件被触发->childonclik事件被触发->parentonclik事件被触发
<script type="text/javascript">
document.getElementById("parent").addEventListener("click",function(e){
alert("parent事件被触发,"+this.id);
},true)
document.getElementById("child").addEventListener("click",function(e){
alert("child事件被触发,"+this.id)
},false)
</script>

//点击'您好',先弹出parent事件被触发,再弹出child事件被触发

应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//要求:鼠标放到li上对应的li背景变灰
<ul>
<li>item1</li>
<li>item2</li>
<li>item3</li>
<li>item4</li>
<li>item5</li>
<li>item6</li>
</ul>
//实现一:利用事件冒泡实现,不用遍历所有li节点
$("ul").on("mouseover",function(e){
$(e.target).css("background-color","#ddd").siblings().css("background-color","white");
})
//实现二:给每个li绑定事件,缺点,动态的加载了一些元素,新增li后,还要再绑定一次事件
$("li").on("mouseover",function(){
$(this).css("background-color","#ddd").siblings().css("background-color","white");
})

禁止冒泡

1
2
3
4
5
6
7
8
9
function doSomething(e) {
if (!e) {//微软模型
var e = window.event;
e.cancelBubble = true;
}
if (e.stopPropagation) {//w3c事件模型
e.stopPropagation();
}
}

补充

事件委托是什么?有什么好处?

假设父元素有4个儿子,我不监听4个儿子,而是监听父元素,看触发事件的元素是哪个儿子,这就是事件委托。
可以监听还没有出生的儿子(动态生成的元素)。省监听器。

My Little World

关于Performance API

发表于 2017-08-03

load/unload事件

load事件

当页面完全加载后(包括所有图像、JavaScript文件、CSS文件等外部资源),就会触发window上面的load事件
使用方法

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
//方法一
var EventUtil = {
addHandler: function(element, type, handler){
if (element.addEventListener) {
element.addEventListener(type, handler, false);
} else if (element.attachEvent) {
element.attachEvent("on" + type, handler);
} else {
element["on" + type] = handler;
}
}
}
}
EventUtil.addHandler(window,"load",function(event){
alert("loaded!");
});
//方法二
<!DOCTYPE html>
<html>
<head>
<title>load Event Example</title>
</head>
<body onload = "alert("Loaded!")">
</body>
</html>

document.ready 监控dom是否加载完毕,dom加载完毕时及资源加载之前触发
DOMContentLoaded 当页面的DOM树解析好并且需要等待js执行完才触发

unload事件

在文档被完全卸载后触发,只要用户切换到另一个页面,就会发生unload事件。
利用这个事件最多的情况是清除引用,以避免内存泄漏
使用方法

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
//方法一
var EventUtil = {
getEvent: function (event) {
return event ? event : window.event;
},
addHandler: function (element, type, handler) {
if (element.addEventListener) {
element.addEventListener(type, handler, false);
} else if (element.attachEvent) {
element.attachEvent("on" + type, handler);
} else {
element["on" + type] = handler;
}
}
};
EventUtil.addHandler(window, "unload", function (event) {
alert("Unloaded");
})
//方法二
<html>
<head>
<tilte>卸载(unload)事件</tile>
</head>
<body onunload="alert('Unload')">
</body>
</html>

Performance API

Performance 接口会给出某个页面的与时间相关的性能信息
使用方法为 window.performance.属性/方法

Performance API用于精确度量、控制、增强浏览器的性能表现。这个API为测量网站性能,提供以前没有办法做到的精度
目前,所有主要浏览器都已经支持performance对象,包括Chrome 20+、Firefox 15+、IE 10+、Opera 15+。

performance.timing

包含各种与浏览器性能有关的时间数据,提供浏览器处理网页各个阶段的耗时

  • navigationStart:当前浏览器窗口的前一个网页关闭,发生unload事件时的Unix毫秒时间戳。如果没有前一个网页,则等于fetchStart属性。即浏览器处理当前网页的启动时间
  • unloadEventStart:如果前一个网页与当前网页属于同一个域名,则返回前一个网页的unload事件发生时的Unix毫秒时间戳。如果没有前一个网页,或者之前的网页跳转不是在同一个域名内,则返回值为0。
  • unloadEventEnd:如果前一个网页与当前网页属于同一个域名,则返回前一个网页unload事件的回调函数结束时的Unix毫秒时间戳。如果没有前一个网页,或者之前的网页跳转不是在同一个域名内,则返回值为0。
  • redirectStart:返回第一个HTTP跳转开始时的Unix毫秒时间戳。如果没有跳转,或者不是同一个域名内部的跳转,则返回值为0。
  • redirectEnd:返回最后一个HTTP跳转结束时(即跳转回应的最后一个字节接受完成时)的Unix毫秒时间戳。如果没有跳转,或者不是同一个域名内部的跳转,则返回值为0。
  • fetchStart:返回浏览器准备使用HTTP请求读取文档时的Unix毫秒时间戳。该事件在网页查询本地缓存之前发生。
  • domainLookupStart:返回域名查询开始时的Unix毫秒时间戳。如果使用持久连接,或者信息是从本地缓存获取的,则返回值等同于fetchStart属性的值。
  • domainLookupEnd:返回域名查询结束时的Unix毫秒时间戳。如果使用持久连接,或者信息是从本地缓存获取的,则返回值等同于fetchStart属性的值。
  • connectStart:返回HTTP请求开始向服务器发送时的Unix毫秒时间戳。如果使用持久连接(persistent connection),则返回值等同于fetchStart属性的值。
  • connectEnd:返回浏览器与服务器之间的连接建立时的Unix毫秒时间戳。如果建立的是持久连接,则返回值等同于fetchStart属性的值。连接建立指的是所有握手和认证过程全部结束。
  • secureConnectionStart:返回浏览器与服务器开始安全链接的握手时的Unix毫秒时间戳。如果当前网页不要求安全连接,则返回0。
  • requestStart:返回浏览器向服务器发出HTTP请求时(或开始读取本地缓存时)的Unix毫秒时间戳。
  • responseStart:返回浏览器从服务器收到(或从本地缓存读取)第一个字节时的Unix毫秒时间戳。
  • responseEnd:返回浏览器从服务器收到(或从本地缓存读取)最后一个字节时(如果在此之前HTTP连接已经关闭,则返回关闭时)的Unix毫秒时间戳。
  • domLoading:返回当前网页DOM结构开始解析时(即Document.readyState属性变为“loading”、相应的readystatechange事件触发时)的Unix毫秒时间戳。
  • domInteractive:返回当前网页DOM结构结束解析、开始加载内嵌资源时(即Document.readyState属性变为“interactive”、相应的readystatechange事件触发时)的Unix毫秒时间戳。
  • domContentLoadedEventStart:返回当前网页DOMContentLoaded事件发生时(即DOM结构解析完毕、所有脚本开始运行时)的Unix毫秒时间戳。
  • domContentLoadedEventEnd:返回当前网页所有需要执行的脚本执行完成时的Unix毫秒时间戳。
  • domComplete:返回当前网页DOM结构生成时(即Document.readyState属性变为“complete”,以及相应的readystatechange事件发生时)的Unix毫秒时间戳。
  • loadEventStart:返回当前网页load事件的回调函数开始时的Unix毫秒时间戳。如果该事件还没有发生,返回0。
  • loadEventEnd:返回当前网页load事件的回调函数运行结束时的Unix毫秒时间戳。如果该事件还没有发生,返回0。

unloadEventStart 不等于navigationStart,navigationStart比unloadEventStart早
domComplete 等于 loadEventStart

performance.now()

performance.now方法返回当前网页自从performance.timing.navigationStart到当前时间之间的微秒数(毫秒的千分之一)。

performance.mark()

mark方法用于为相应的视点做标记。
window.performance.mark(‘mark_fully_loaded’);
clearMarks方法用于清除标记,如果不加参数,就表示清除所有标记。
window.peformance.clearMarks(‘mark_fully_loaded’);
window.performance.clearMarks();

performance.getEntries()

浏览器获取网页时,会对网页中每一个对象(脚本文件、样式表、图片文件等等)发出一个HTTP请求。performance.getEntries方法以数组形式,返回这些请求的时间统计信息,有多少个请求,返回数组就会有多少个成员.
由于该方法与浏览器处理网页的过程相关,所以只能在浏览器中使用。
window.performance.getEntries()[0] //获取第一个HTTP请求(即网页的HTML源码)的时间统计信息

performance.navigation对象

1、performance.navigation.type
该属性返回一个整数值,表示网页的加载来源,可能有以下4种情况:

  • 0:网页通过点击链接、地址栏输入、表单提交、脚本操作等方式加载,相当于常数performance.navigation.TYPE_NAVIGATENEXT。
  • 1:网页通过“重新加载”按钮或者location.reload()方法加载,相当于常数performance.navigation.TYPE_RELOAD。
  • 2:网页通过“前进”或“后退”按钮加载,相当于常数performance.navigation.TYPE_BACK_FORWARD。
  • 255:任何其他来源的加载,相当于常数performance.navigation.TYPE_UNDEFINED。

2、performance.navigation.redirectCount
该属性表示当前网页经过了多少次重定向跳转.
参考资料

页面请求加载过程

var t = performance.timing;

1、首先,在浏览器地址栏中输入url

2、浏览器先查看浏览器缓存-系统缓存-路由器缓存,如果缓存中有,会直接在屏幕中显示页面内容。若没有,则跳到第三步操作。

3、在发送http请求前,需要域名解析(DNS解析),解析获取相应的IP地址。
var dns = t.domainLookupEnd - t.domainLookupStart; //域名解析时间
4、浏览器向服务器发起tcp连接,与浏览器建立tcp三次握手。
var tcp = t.connectEnd - t.connectStart;//浏览器与服务器之间的连接建立时间
5、握手成功后,浏览器向服务器发送http请求,请求数据包。
t.requestStart 浏览器向服务器发出HTTP请求时(或开始读取本地缓存时)的时间
6、服务器处理收到的请求,将数据返回至浏览器

7、浏览器收到HTTP响应
var ttfb = t.responseStart - t.navigationStart;//读取页面第一个字节之前的耗时
8、读取页面内容,浏览器渲染,解析html源码
var getdata = t.responseEnd-t.responseStart;//接收数据时间
9、生成Dom树、解析css样式、js交互
var dom = t.domInteractive-t.domLoading; //从构建DOM到页面与用户可以开始交互的时间
var script= t.domContentLoadedEventEnd-t.domContentLoadedEventStart;//脚本运行时间
var load = t.loadEventEnd-t.loadEventStart;//load事件处理时间

10、客户端和服务器交互

11、ajax查询

步骤二详细步骤

  • 浏览器缓存:浏览器会记录DNS一段时间,因此,只是第一个地方解析DNS请求;
  • 操作系统缓存:如果在浏览器缓存中不包含这个记录,则会使系统调用操作系统,获取操作系统的记录(保存最近的DNS查询缓存);
  • 路由器缓存:如果上述两个步骤均不能成功获取DNS记录,继续搜索路由器缓存;
  • ISP缓存:若上述均失败,继续向ISP搜索。

浏览器渲染步骤

  • 解析html以构建dom树 分词器器、解析器
  • 构建render树
  • 布局render树
  • 绘制render树

渲染引擎开始解析html,并将标签转化为内容树中的dom节点。接着,它解析外部CSS文件及style标签中的样式信息。这些样式信息以及html中的可见性指令将被用来构建另一棵树——render树。

Render树由一些包含有颜色和大小等属性的矩形组成,它们将被按照正确的顺序显示到屏幕上。

Render树构建好了之后,将会执行布局过程,它将确定每个节点在屏幕上的确切坐标。再下一步就是绘制,即遍历render树,并使用UI后端层绘制每个节点

现代浏览器工作原理

Navigator 对象

包含有关浏览器的信息。
appCodeName 返回浏览器的代码名
appName 返回浏览器的名称
appVersion 返回浏览器的平台和版本信息
cookieEnabled 返回指明浏览器中是否启用 cookie 的布尔值
platform 返回运行浏览器的操作系统平台
userAgent 返回由客户机发送服务器的user-agent 头部的值

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
js 获取操作系统
function detectOS() {
var sUserAgent = navigator.userAgent;
var isWin = (navigator.platform == "Win32") || (navigator.platform == "Windows");
var isMac = (navigator.platform == "Mac68K") || (navigator.platform == "MacPPC") || (navigator.platform == "Macintosh") || (navigator.platform == "MacIntel");
if (isMac) return "Mac";
var isUnix = (navigator.platform == "X11") && !isWin && !isMac;
if (isUnix) return "Unix";
var isLinux = (String(navigator.platform).indexOf("Linux") > -1);
if (isLinux) return "Linux";
if (isWin) {
var isWin2K = sUserAgent.indexOf("Windows NT 5.0") > -1 || sUserAgent.indexOf("Windows 2000") > -1;
if (isWin2K) return "Win2000";
var isWinXP = sUserAgent.indexOf("Windows NT 5.1") > -1 || sUserAgent.indexOf("Windows XP") > -1;
if (isWinXP) return "WinXP";
var isWin2003 = sUserAgent.indexOf("Windows NT 5.2") > -1 || sUserAgent.indexOf("Windows 2003") > -1;
if (isWin2003) return "Win2003";
var isWinVista= sUserAgent.indexOf("Windows NT 6.0") > -1 || sUserAgent.indexOf("Windows Vista") > -1;
if (isWinVista) return "WinVista";
var isWin7 = sUserAgent.indexOf("Windows NT 6.1") > -1 || sUserAgent.indexOf("Windows 7") > -1;
if (isWin7) return "Win7";
}
return "other";
}

document.writeln("您的操作系统是:" + detectOS());

js判断浏览器

1…151617…25
YooHannah

YooHannah

246 日志
1 分类
21 标签
RSS
© 2025 YooHannah
由 Hexo 强力驱动
主题 - NexT.Pisces