My Little World

learn and share


  • 首页

  • 分类

  • 标签

  • 归档

  • 关于
My Little World

关于webpack

发表于 2017-10-08

说明

1.webpack工作内容
Webpack可以看做是模块打包机:它做的事情是,分析你的项目结构,找到JavaScript模块以及其它的一些浏览器不能直接运行的拓展语言(Scss,TypeScript等),并将其转换和打包为合适的格式供浏览器使用。
2.webpack工作方式
把你的项目当做一个整体,通过一个给定的主文件(如:index.js),Webpack将从这个文件开始找到你的项目的所有依赖文件,使用loaders,plugins处理它们,最后打包为一个(或多个)浏览器可识别的JavaScript文件。

使用

1.如果在nodejs中全局安装了webpack和webpack-dev-server,可以在git bash直接使用’webpack’命令进行打包,使用’webpack-dev-server’命令启动打包后生成的项目
2.在打包之前,要先配置webpack.config.js文件,’webpack’命令会根据webpack.config.js文件的配置项去打包文件
3.如果想使用不同的配置文件去打包项目,可以使用’webpack –config 文件名’命令去打包
4.如果不想带文件名去打包,可以在package.json的scripts对象中配置命令‘xxx’,然后在git bash中使用命令‘npm run xxx’进行打包

1
2
3
4
5
"scripts": {
"start": "webpack-dev-server",
"build": "webpack --config webpack.config.build.js",
"dev":"webpack --config webpack.config.dev.js"
}

执行 npm run build 则按照webpack.config.build.js文件配置的内容去打包
同样,执行 npm run dev 则按照webpack.config.dev.js文件配置的内容去打包
但要注意,使用配置命令’start’时,直接执行’npm start’,就可以执行其对于的命令,中间不需要加run

webpack.config.js 配置

module.exports = {}
1.配置根路径
配置入口文件前,可以用context配置根路径,这样在配置时,就不用将文件路径写进去,直接将文件配置进去就可以了
可以借助path模块拼接路径字符串

1
2
3
4
5
6
7
8
const path = require('path')

module.exports = {
/**
* The base directory
*/
context: path.join(__dirname, './src'),//这样入口文件就是指src文件夹下面的那些文件
}

2.入口文件
入口文件是指打包后项目生成的可以访问的文件

1
2
3
4
5
6
7
8
9
//单入口:
entry: './index.js',/*打包单个文件*/
entry: ['./index1.js','./index2.js'],/*打包多个文件*/

//多入口:
entry: {
home: './home',
user: ['./user', './account']
},

多入口文件数组的KEY值可用于命名输出文件名,其对应的值即是要打包的源文件

3.输出文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
output: {

/* filename方式一:直接指定文件名*/
filename: 'bundle.js',

/* filename方式二:映射变量[name]和[hash],[name]即定义入口文件的key值,[hash]是webpack在打包的时候按照一定规则生成的值,是MD5加密的值,如果文件没有发生变化,hash值不会变,可用于版本控制
(CDN一般缓存时间比较长,文件发生变化,hash值就会变,文件名就会变,CDN就能及时更新文件)
* 还可以加[chunkhash],这里是作为版本号使用,方便上线时静态资源的版本管理
* 单入口[name]被映射成‘main’
*/
filename: '[name].bundle.[hash].js',

path: '/home/proj/public/assets',/*指定输入文件存放路径*/
libraryTarget:
}

其他配置参数

4.配置加载器loader

loader 是对应用程序中资源文件进行转换。它们是(运行在 Node.js 中的)函数,可以将资源文件作为参数的来源,然后返回新的资源文件。
官方可用loader

Loaders的配置包括以下几方面:
test:一个用以匹配loaders所处理文件的拓展名的正则表达式(必须)
loader:loader的名称(必须)
include/exclude:手动添加必须处理的文件(文件夹)或屏蔽不需要处理的文件(文件夹)(可选);
query:为loaders提供额外的设置选项(可选)

loader被配置在rules数组中,数组每一个对象可配置一个或多个loader
例:

1
2
3
4
5
6
7
8
9
module: {
rules: [
{
test: /\.js$/,
include: path.join(__dirname, './src'),
use: 'babel-loader'
}
]
},

常用loader:
babel-loader:编译js,考虑到babel具有非常多的配置选项,一般把babel的配置选项放在一个单独的名为 “.babelrc” 的配置文件中
css-loader:使你能够使用类似@import 和 url(…)的方法实现 require()的功能,获取css文件里面的样式
style-loader:将css样式插件js文件,与css-loader一起使用能够把样式表嵌入webpack打包后的JS文件中
autoprefixer-loader:自动添加前缀,使css文件更有兼容性
postcss-loader:css会自动根据Can i use里的数据添加不同前缀,可与autoprefixer-loader结合使用
ExtractTextPlugin.extract:将css单独打包成一个.css文件,使用这个插件,需要require 插件extract-text-webpack-plugin

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
//配置代码:
1.
module: {
rules: [
{
test: /\.css$/,
include: [
path.join(__dirname, './src')
],
use: ['style-loader', 'css-loader']
//链式调用loader的时候,会以数组逆序的顺序执行,
//先执行css-loder,再执行style-loader
}
]
},

2.
module: {
rules: [{
test: /\.css$/,
include: [
path.join(__dirname, './src'),
],
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: 'css-loader'
})
}]
},

plugins: [
new ExtractTextPlugin('[name].css')
]
3.
module: {
rules: [{
test: /\.css$/,
include: [
path.join(__dirname, './src')
],
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: ['css-loader', 'autoprefixer-loader']
})
}]
},
plugins: [
new ExtractTextPlugin('[name].css')
]

5.配置插件plugin
插件目的在于解决 loader 无法实现的其他事,配置插件都在plugin数组中,数组每一项就是new一个plugin,插件的配置也在new时以参数形式传递进去
例:

1
2
3
4
5
6
7
8
9
//打包公共文件方式一:

plugins: [
new CommonsChunkPlugin({
name: 'commons',
filename: 'commons.js'
}),
new ExtractTextPlugin('[name].css')
]

常用插件:
CommonsChunkPlugin:将多个文件通用的东西打包成一个文件
webpack.optimize.CommonsChunkPlugin:require(‘webpack’),使用webpack的optimize.CommonsChunkPlugin将打包的文件转化为公共文件
HtmlWebpackPlugin:是依据一个简单的index.html模板,生成一个自动引用你打包后的JS文件的新index.html。这在每次生成的js文件名称不同时非常有用(比如添加了hash值)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//打包公共文件方式二:

var path = require('path')
var webpack = require('webpack')

module.exports = {
context: path.join(__dirname, './src'),
entry: {
app: './',
vendor: ['jquery', 'underscore'],//将文件中require的'jquery', 'underscore'打包成公共文件vendor.bundle.js
},
output: {
path: path.join(__dirname, 'dist'),
filename: 'bundle.js'
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
filename: 'vendor.bundle.js'
})
]
}

入门Webpack

写一个webpack 的loader

1.相当于写一个普通的模块

1
2
3
4
5
module.exports = function (source){
.....
...
return xxx
}

2.获取配置参数
方式一
直接使用API:this.query
方式二
使用loader-utils库

1
2
const loaderutils = require('loader-utils')
const options = loaderutils.getOptions(this);

3.处理模块依赖
方式一:转化成require语句
例如,css-loader将@import和url(…)替换会require(…)
方式二:使用this.resolve函数解析路径
例如,less-loader将less编译器进行扩展自定义路径解析逻辑然后利用this.resolve解析依赖

4.不要使用绝对路径
5.如果要包裹另外一个包将这个包作为peerDependency在package.json中引入
6.返回值
如果只是返回一个值可以用return
如果返回多个值需要调用API:this.callback(),但依然要在最后return一个undefined

7.本地配置使用

单个:path.resolve(‘../../loader.js’)

1
2
3
4
5
6
7
8
9
{
test: /\.js$/
use: [
{
loader: path.resolve('path/to/loader.js'),
options: {/* ... */}
}
]
}

多个或一个

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
module: {
rules: [
{
test: /\.js/,
use: [{
loader:'define-loader',
options:{name:'alice'}
}
],
}
]
},
resolveLoader: {
modules: [
'node_modules',
path.resolve(__dirname, 'loaders')
]
}

中文
How to write a loader
loader API

写一个webpack 的plugin

按照格式,根据compiler 和 compilation 对象处理流程上的钩子函数,
在不同阶段插入我们要做的事情
1.格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class DefPlugin {
constructor(name) {
}

apply(compiler) {
// 指定挂载的webpack事件钩子。
compiler.plugin("compile", function(params) {
console.log("The compiler is starting to compile...");
});
// 指定挂载的webpack事件钩子。
compiler.plugin('emit', function(compilation, callback) {
// 现在设置回调来访问编译中的步骤:
compilation.plugin("optimize", function() {
console.log("Assets are being optimized.");
});
//功能完成后调用webpack提供的回调。
callback();

})
}
}
module.exports = DefPlugin

2.使用
直接在webpack.config.js文件中require一下就好

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

const Plugin = require('./myplugin')

module.exports = {
context: path.join(__dirname, 'src'),
entry: './index.js',
output: {
libraryTarget: 'commonjs2',
path: path.join(__dirname, 'dist'),
filename: './bundle.js'
},
plugins: [
new Plugin
]
}

3.compiler
compiler 对象代表了完整的 webpack 环境配置。这个对象在启动 webpack 时被一次性建立,并在所有可操作的设置中被配置,包括原始配置,loader 和插件。当在 webpack 环境中应用一个插件时,插件将收到一个编译器对象的引用。可以使用它来访问 webpack 的主环境。
源码
4.compilation
compilation 对象代表了一次单一的版本构建和生成资源。当运行 webpack 开发环境中间件时,每当检测到一个文件变化,一次新的编译将被创建,从而生成一组新的编译资源。一个编译对象表现了当前的模块资源、编译生成资源、变化的文件、以及被跟踪依赖的状态信息。编译对象也提供了很多关键点回调供插件做自定义处理时选择使用
源码
5.tabable

webpack性能优化

1.将不再使用的依赖及时删除
2.不要为了实现小功能引入大型lib
3.合理配置babel
4.根据使用场景合理使用优化插件
例如使用happypack,webpack-uglify-parallel 在开发阶段进行多核并行压缩
相关链接
学习链接

gulp

api:
task:
src:
watch:用来监视文件的变化;

jshint:js 代码检查
uglify:压缩js
minify-css:压缩css
minfy-html:压缩html
concat:合并文件

babel

babel是一个js转换器
可以将es6,es7,react/jsx的语法通过他的插件转编译成es5的或者说支持在现有环境运行的语法
使用babel,除了可以使用package.json进行配置,一般会为它建立专门的.babelrc文件进行配置

1
2
3
4
{
"presets": [],
"plugins": []
}

使用babel-preset-env转换es5,就将‘env’添加到.babelrc的presents数组中
使用babel-preset-react转换jsx,就将‘react’添加到.babelrc的presents数组中
使用babel-preset-stage-x转换es7不同阶段语法,就将‘stage-x’添加到.babelrc的presents数组中,x目前可以为0,1,2,3
使用babel-core模块,可以在代码中手动调用babelAPI进行转码
使用babel-polyfill模块,可以转换新版es6/es7里面的API,如generator,Symbol,Promise等
安装babel-cli实现命令行进行转码
另外babel经常用于前置转码,比如与eslint结合进行代码检查,与mocha进行脚本测试

My Little World

关于HTTP

发表于 2017-10-05

在浏览器的地址栏输入url后发生了什么

粗略的回答的话,就是浏览器向服务器发送该url,浏览器根据该url找到相应的资源,再把资源返回给浏览器,即使我们看到的页面内容
细致的回答的话,浏览器会在应用层利用DNS协议解析域名生成ip地址,利用http协议生成一个http请求然后传递给传输层,传输层将根据TCP协议将这个http请求报文进行分割并打上序号标记、端口号和三次握手标志后转发给网络层,网络层会利用ARP协议根据ip地址查到MAC地址,IP协议对数据封装MAC地址等信息,将打包好的数据包发送给链路层再封装然后发送出去,经过一系列转发,忽略路由、CDN的缓存策略,中转到达服务器后,服务器再按链路层,网路层,传输层,应用层的顺序,从下到上依次解封数据信息,找到资源后,同样分割,封装打包再传给客户端浏览器。

重新看待这个问题

流程图1
流程图2
browerurl

浏览器渲染过程

解析HTML构建DOM树时,渲染引擎会先将HTML元素标签解析成
由多个DOM元素对象节点组成且具有节点父子关系的DOM树结构,
然后根据DOM树结构的每个节点顺序提取计算使用的CSS规则
并重新计算DOM树结构的样式数据,
生成一个带有样式描述的dom渲染树对象
DOM渲染树生成结束后进入渲染树的布局阶段
即根据每个渲染树节点在页面中的大小和位置,将节点固定到页面对应位置上
这个阶段主要是元素的布局属性(position,float,margin等属性)
即在浏览器中绘制页面上元素节点的位置
绘制阶段将渲染树节点的背景,颜色,文本等样式信息应用到每个节点上
这个阶段主要是元素的内部显示样式(color,background,text-shadow等属性)生效
最终完成整个DOM在页面上的绘制显示

不同浏览器内核渲染过程有区别
Webkit内核(google,safari)中HTML和css解析可以认为是并行的,生成的渲染对象被叫做渲染树(render tree)
Geoko内核(firefox)则是先解析html生成sink后再开始解析CSS,生成的渲染对象称为Frame树(Frame tree)

在渲染树生成阶段,DOM树中的结点会在CSS分析树中根据元素,类,id选择器来提取与之对应元素的一条或多条CSSRule,进行CSS规则的层叠和权重计算,
得到最终生成的样式CSSRule并添加到DOM渲染树上,
当每个Dom节点提取CSS样式完成时,用于页面布局和绘制的DOM渲染树便形成了
在一个已经形成的DOM渲染树中,节点的CSS规则可通过
document.defaultView.getComputedStyle(element,null)
方法获取

关于 TCP/IP 协议

1.不同的硬件、操作系统之间的通信,所有这一切都需要一种规则,这种规则即协议
2.与互联网相关联的协议集合起来就是TCP/IP
3.http协议是TCP/IP协议的一个子集
4.URI(统一资源标识符),用字符串标识某一互联网资源,而url表示资源的地点

分层管理

好处:局部改变设计,只需把变动的层替换,无需整体替换
应用层:应用服务间通信,如FTP,DNS服务,应用http协议
传输层:网络连接中两台计算机之间数据传输,应用TCP和UDP协议
网络层:处理网络上流动的数据包,应用IP协议
链路层:处理连接网络的硬件之间通信,包括操作系统,硬件的设备驱动,光纤等。

三次握手

目的在于确保准确无误的将数据送达目的地
TCP协议在封装数据时会封装SYN和ACK作为握手标识
发送端首先发送一个带SYN标识的数据包给对方,接收端收到后,回传一个带有SYN/ACK标识的数据包以示传达确认信息,最后,发送端再回传一个带ACK标识的数据包,代表‘握手’结束
若在握手过程中某个阶段莫名中断,TCP协议会再次以相同的顺序发送相同的数据包

HTTP协议

1.请求报文由请求方法、请求URI、协议版本、可选的请求首部字段和内容实体构成
2.响应报文基本上由协议版本、状态码(表示请求成功或失败的数字代码)、用以解释状态码的原因短语、可选的响应首部字段以及实体主体构成
3.http是无状态协议,即不保存之前发送过的请求或响应
4.持久链接的特点:只要任意一端没有明确提出断开连接,则保持TCP连接状态,
旨在建立1次TCP连接后进行多次请求和响应的交互
5.管线化技术:在持久链接状态下,不用等待响应亦可直接发送下一个请求,实现并行发送多个请求
6.请求方法
GET:访问已被URI识别的资源,获取响应内容
POST:传输实体的主体
PUT:在请求报文主体中包含文件内容,然后保存到请求URI指定的位置,自身不带验证机制
HEAD:用于确认URI的有效性及资源更新的日期时间等
DELETE:按请求URI删除指定的资源,不带验证机制
OPTION:查询针对请求URI指定的资源支持的方法
TRACE: 将之前的请求通信环回给客户端,可以查询发送出去的请求是怎样被加工修改/篡改的,但容易引发XST攻击
CONNECT:在与代理服务器通信时建立隧道,实现用隧道协议进行TCP通信。主要使用SSL和TLS协议把通信内容加密后经网络隧道传输

COOKIE状态管理

客户端在未携带cookie信息状态下请求服务器时,服务器会生成cookie,并记住是向谁发送的,然后在响应报文头中添加一个叫Set-Cookie的字段信息,通知客户端保存Cookie。
当下次客户端再往该服务器发送请求时,客户端会自动在请求报文中加入Cookie值后发送出去
服务器端发现客户端发送过来的Cookie后,会去检查究竟是从哪一个客户端发来的连接请求,然后对比服务器上的记录,最后得到之前的状态信息.
7.报文由报文首部、空格和报文主体构成

请求报文的报文首部分为:请求行(请求方法,URI,HTTP版本)、请求首部字段、通用首部字段、实体首部字段、其他
响应报文的报文首部分为:状态行(状态码,原因短语,HTTP版本)、响应首部字段、通用首部字段、实体首部字段、其他

报文主体即传输请求或响应的实体主体

提升传输效率采取的方法:传输编码,对实体进行‘内容编码’,压缩传输内容,分块传输编码

发送不同类型数据时,需要首部字段Content-Type
请求报文的Content-Type赋值mulitipart/form-data,
响应报文的Content-Type赋值mulitipart/byteranges,

请求指定范围数据时,可以用首部字段Range指定资源的byte范围
例:请求开始到3000,和5000到7000字节的多重范围
Range:bytes=-3000,5000-7000
响应状态码会返回206,多重范围会在首部添加Content-Type:mulitipart/byteranges
如果服务器无法响应范围请求,则会返回状态码200OK和完整的实体内容

内容协商机制:客户端和服务端就响应的资源内容进行交涉,然后提供给客户端最为适合的资源。
内容协商会以响应资源的语言、字符集、编码方式等作为判断的基准
对应首部字段有:Accept,Accept-Charset,Accept-Encoding,Accept-Language,Content-Language
内容协商技术有三类:
服务器驱动协商:以请求首部字段为参考,在服务器端自动处理
客户端驱动协商:用户在浏览器可选列表中手动选择
透明协商:前两者的结合体,由服务端和客户端各自进行内容协商的一种方法

8.状态码

1XX:请求正在处理
2XX:请求正常那个处理完毕
3XX:重定向,需要进行附加操作以完成请求,
4XX:服务器无法处理请求,客户端有错误发生
5XX:服务器处理请求出错

200 OK 客户端的请求在服务器被正常处理了
204 No Content ,正常处理,但响应不含实体
206 Partial Content 成功处理了范围请求,响应只含指定范围实体

301 Moved Permanently 永久重定向
302 Found 临时重定向
303 See Other 临时重定向,但只能使用get方法访问该资源
304 Not Modified 服务器没有找到符合客户端附带条件(If-Match,If-Range等报文首部)的资源,响应不含主体,与重定向无关,服务器告诉客户,原来缓冲的文档还可以继续使用
307 Temporary Redirect 临时重定向,按照浏览器标准不会从post变成GET。但浏览器不同,出现情况也可能不同

400 Bad Request 请求报文存在语法错误
401 Unauthorized 携带信息未通过HTTP认证,没有权限访问资源
403 Forbidden 请求的资源被服务器拒绝访问
404 Not Found 无法找到请求资源

500 Internal Server Error 服务器在执行请求时发生了错误,也可能是web应用存在bug或临时故障
502 bad gateway 网关错误
503 Service Unavailable 服务器暂时处于超负载或正在进行停机维护

9.通信数据转发程序
代理:接收客户端请求并转发给源服务器,不改变URI,但在转发时会添加via首部
好处:利用缓存技术减少网络带宽的流量,组织内部针对特定网站的访问控制,以获取访问日志为主要目的等等
使用方法分两类:
1.是否使用缓存,例,缓存代理(预先将资源副本存在代理服务器上);
2.是否会修改报文,例,透明代理(对报文不做任何加工,反之非透明代理)

网关:转发其他服务器数据的服务器,接收从客户端发来的请求并对请求进行处理
好处:能使通信线路上服务器提供非HTTP协议服务,能提高通信安全性

隧道:在相隔甚远的客户端和服务器之间进行中转,并保持双方通信连接的应用程序
目的:确保客户端能与服务器进行安全的通信,通信双发断开连接时结束

缓存:代理服务器或客户端本地磁盘内保存的资源副本 ,其实相当于是一种代理服务器
目的:减少对源服务器的访问,从而节省通信流量和通信时间
机制:超过有效期之后,会向源服务器确认资源有效性,若判断缓存失效,则再次从源服务器上获取‘新’资源

10.首部字段
分两类:缓存代理和非缓存代理
缓存代理,即端到端首部,这类首部会被带到客户端或服务端,且会保存在缓存中
非缓存代理,即逐跳首部,只对单词转发有效,会因通过缓存或代理而不再转发

通用首部:客户端和服务端都会用的首部
header1
请求首部
header2
响应首部
header3
实体首部
header4
cookie 相关首部
header5
一些需要注意的字段
connection:close 完成本次响应后,无需等待后续请求,断开链接
connection:keep-alive 完成本次响应后保持链接等待后续请求

connection:upgrade
upgrade:websocket
这两者组合在一起时,表示客户端申请的更换协议,
服务端允许切换,此时报头为101,表示还需要等待完成切换的过程

content-Encoding 表示服务端压缩方法
Transfer-Encoding 表示服务端编码方法

https

http缺点

1.通信使用明文(不加密),内容可能会被窃听
造成原因:通信线路上的网络设备、光纤等不可能是个人私有物,不排除某个环节会遭到恶意窥视或窃听
解决办法:加密处理防止被窃听
方式一:通信加密,使用SSL和TLS建立安全通信线路,再在这条线路上进行HTTP通信
方式二:内容加密,对HTTP协议传输的内容本身加密,前提要求服务端和客户端都具备加密解密机制

2.不验证通信方的身份,因此可能遭遇伪装
造成原因:服务器不管谁发来的请求都会给一个响应,客户端不管是谁的响应都接收
解决办法:查明对方证书,证书由第三发颁发,通信前先确认对方证书,验证时要通信的双方后,再继续通信

3.无法证明报文完整性,所以有可能已遭篡改
中间人攻击:请求或响应在传输途中,遭攻击者拦截并篡改内容的攻击
造成原因:中间人攻击
解决办法:MD5和SHA-1等散列值校验的方法,以及用来确认文件的数字签名方法,但以上方法都需要用户本人自己验证

HTTP+加密+认证+完整性保护 = HTTPS

HTTP通信接口部分用SSL和TLS协议代替,即不再是http与TCP通信,而是http和SSL再和TCP通信
共享/对称密钥加密:加密和解密用同一个密钥
公开密钥加密:
使用一对非对称的密钥,一把叫做私有密钥,另一把叫做公开密钥,
使用公开密钥进行加密发送密文,收信方收到信息后用私有密钥进行解密
公开密钥加密方式比共享密钥加密方式处理起来更麻烦

HTTPS混合加密:在交换密钥环节使用公开密钥加密方式,之后的建立通信交换报文阶段则使用共享密钥加密方式
缺点:无法保证公开密钥本身是真正的公开密钥
解决办法:使用数字证书进行验证,客户端根据数字证书机构的公开密钥验证数字签名,从而验证服务器公开密钥
header6
关于证书:
客户端证书一般由银行等特殊业务颁发,因为客户端证书需要费用支出
自签名证书无法保证通信真实性
中级认证机构的证书可能变成自认证证书
HTTPS的安全通信机制
header7
header8
步骤12结束之后,客户端再发送TCP FIN报文来关闭与TCP的通信
缺点:
1.由于加密解密过程消耗大量CPU以及内存等资源,导致处理速度变慢
2.由于SSL通信部分消耗网络资源,处理通信部分又会消耗一定时间,因此相比http又会变慢
解决办法:使用SSL加速器以分担负载

不使用https原因:
1.客户端要考虑消耗cpu,内存资源,服务器端要考虑负载
2.购买证书需要开销

用户认证

方法有BASIC认证,DIGEST认证,SSL客户端认证,FormBase认证

HTTP/2.O

http2.0协议优点
1.采用二进制进行流式传输,使用HPACK压缩消息头,节省宽带
2.使用tcp多路复用,降低网络请求链接时建立关闭的开销
这里要与区别keep-alive作区分
keep-alive发生在应用层,而tcp复用发生在传输层
keep-alive多文件只能串行传输,一个传完再传一个,而TCP多文件可以并行同时传输
3.可以控制传输流优先级和流量
4.支持服务端推送

多路复用

解决的问题

在http/1.x下,浏览器客户端在同一时间,针对同一域名下的请求有一定数量限制。超过限制数目的请求会被阻塞
突发性和短时性的 HTTP 连接因为三次握手和慢启动的存在,非常浪费时间(–>单一长连接)
(慢启动:TCP连接在一开始会限制连接最大速度,确认数据能够成功传输后,随时间推移才会慢慢提高传输速度)

实现–二进制分帧

在应用层和传输层之间增加二进制分帧层,应用层生成的报文会分割封装为更小的消息和帧,并进行二进制编码
其中首部header和请求主体request body分别被封装为header frame 和data frame
同一请求或响应的帧会有相同标志,会按照一定先后顺序被发送,
所以不同的请求或者响应可以互相穿插着给到对方
最终实现就是
一个包含上百个资源的页面,在http1.x协议下会创建多条TCP链接来进行串行请求
在http/2.0下只会创建一个长TCP链接,所有的资源请求在压缩处理后一次性发出去,并行地在一个TCP连接上双向交换信息
(同一链接上有多个不同方向的数据流在传输。客户端可以一边乱序发送stream,也可以一边接收服务器的响应,而服务器那端同理。)

而且可以对资源设置优先级,优先级能动态的被改变,优先级高的资源能够被更快的下载下来并得到执行

效果

由于 TCP 连接的减少而使网络拥塞状况得以改善,使拥塞和丢包恢复速度更快
单一长连接多资源的方式,减少服务端的链接压力,避免网络开销,内存占用更少,提高连接吞吐量

压缩头部

通信连接建立之后,由于cookie和user agent很容易膨胀,
每次请求的头部如果有相同的部分,还要以纯文本传输重复发送的话,就会造成流量浪费
因此在客户端和服务端都存放一个首部表,来跟踪和存储之前发送的键值对,对于相同的头部,不必再通过请求发送,只需发送一次
如果首部发生变化,就在压缩时只将变化的数据放在header帧里面进行发送,
无论是客户端还是服务器端收到首部帧后,就对首部表进行更新

服务器推送

就是客户端请求一次资源,服务器给多个响应,这些多个响应很有可能是浏览器解析Html后继续要请求的资源,即主动将资源推送给客户端
而客户端可以做的就是可以将他们缓存起来做备用

延伸问题

1.如何解决推送资源重复或不需要的问题?
客户端使用一个简洁的 Cache Digest 来告诉服务器,哪些东西已经在缓存,因此服务器也就会知道哪些是客户端所需要的。
相关链接

2.为什么HTTP2能去掉SSL在HTTP1.x上的开销
单一的长连接,减少了SSL握手的开销
头部被压缩,减少了数据传输量
多路复用能大幅提高传输效率,不用等待上一个请求的响应
不用像http1.x那样把多个文件或者资源弄成一个文件或者资源(http1.x常见的优化手段),
这时候,缓存就能更容易命中啊(http1.x里面你揉成一团的东西怎么命中缓存?)
相关链接

相关链接

遗留疑问

二进制分帧和TCP切割分包是如何具体实现的,二进制分帧优势具体在哪里?
http/2.0各层协议具体工作
待读

补充

从输入 URL 到页面展现中间发生了什么?<另外的角度看问题>

DNS 查询 DNS 缓存
建立 TCP 连接(三次握手)连接复用
发送 HTTP 请求(请求的四部分)
后台处理请求
监听 80 端口
路由
渲染 HTML 模板
生成响应
发送 HTTP 响应
关闭 TCP 连接(四次挥手)
解析 HTML
下载 CSS(缓存
解析 CSS
下载 JS(缓存
解析 JS
下载图片
解析图片
渲染 DOM 树
渲染样式树
执行 JS

七层协议
应用层
表示层
会话层
传输层
网络层
数据链路层
物理层

实时通信协议

webSocket
Poll轮询
Long-poll 长轮询 (二维码登录)
DDP(分布式数据协议)[使用json数据格式在客户端与服务端进行数据传输,但存在兼容问题]

My Little World

关于promise

发表于 2017-08-31

promise

Promise 是异步编程的一种解决方案

简单使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var promise = new Promise(function(resolve, reject) {
// ... some code

if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});

promise
.then(function(value) { //resolve(value);回调函数
// success
}, function(error) { // reject(error);回调函数
// failure
})
.catch(function(error) {
// 处理 getJSON 和 前一个回调函数运行时发生的错误
console.log('发生错误!', error);
});

1、Promise对象是一个构造函数,用来生成Promise实例,
2、Promise构造函数的参数是一个函数,该函数有两个参数,分别是两个函数:resolve和 reject
3、resolve函数在promise对象状态从Pending变为Fulfiled时调用,即异步操作成功的时候调用,参数一般为获取的结果,方便回调函数使用,或者另一个promise实例,继续进行回调
reject函数在promise对象状态从Pending变为Rejected时调用,即异步操作失败的时候调用,参数一般为错误Error对象,报出错误
4、Promise实例生成以后,可以用then方法分别指定Resolved状态和Rejected状态的回调函数
5、then方法有两个参数,均为匿名函数,第一个匿名函数为resolve()的定义,第二个参数为reject()的定义
6、Promise对象状态变为Resolved,则会调用then方法指定的回调函数;如果异步操作抛出错误,状态就会变为Rejected,就会调用catch方法指定的回调函数,处理这个错误。
另外,then方法指定的回调函数,如果运行中抛出错误,也会被catch方法捕获。
注意,如果then方法定义了reject(),将不会再调用catch方法,如果then里面没有reject,发生错误时将会调用catch

执行流程

1.then方法会在与promise同步的任务完成之后再执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let promise = new Promise(function(resolve, reject) {
console.log('Promise');
resolve();
});

promise.then(function() {
console.log('Resolved.');
});

console.log('Hi!');
//打印顺序
"Promise"
"Hi!"
"Resolved."

Promise 新建后立即执行,所以首先输出的是Promise。然后,then方法指定的回调函数,将在当前脚本所有同步任务执行完才会执行,所以Resolved最后输出。

2、如果Promise1的resolved返回另一个promise2,那Promise1的then会根据promise2的状态决定执行reject还是resolved

1
2
3
4
5
6
7
8
9
10
11
12
var p1 = new Promise(function (resolve, reject) {
setTimeout(() => reject(new Error('fail')), 3000)
})

var p2 = new Promise(function (resolve, reject) {
setTimeout(() => resolve(p1), 1000)
})

p2
.then(result => console.log(result))
.catch(error => console.log(error))
// Error: fail

上面代码中,p1是一个Promise,3秒之后变为rejected。p2的状态在1秒之后改变,resolve方法返回的是p1。由于p2返回的是另一个 Promise,导致p2自己的状态无效了,由p1的状态决定p2的状态。所以,后面的then语句都变成针对后者(p1)。又过了2秒,p1变为rejected,导致触发catch方法指定的回调函数。

3、在Promise构造函数的参数函数里,在调用resolve()或reject()之后,仍会执行后续语句,但如果是抛错语句将不会执行,同样在抛错语句之后的resolve()调用也不会被执行

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
var promise = new Promise(function(resolve, reject) {
resolve('ok');
console.log('1111')
});
promise
.then(function(value) { console.log(value) })
.catch(function(error) { console.log('报错啦') });
//1111
//ok
//因为立即 resolved 的 Promise 是在本轮事件循环的末尾执行,总是晚于本轮循环的同步任务,所以会先打印1111,,再打印ok

var promise = new Promise(function(resolve, reject) {
return resolve('ok');
console.log('1111')
});
promise
.then(function(value) { console.log(value) })
.catch(function(error) { console.log('报错啦') });
//ok
//return 方法阻止继续执行后续操作

var promise = new Promise(function(resolve, reject) {
resolve('ok');
throw new Error('test');
});
promise
.then(function(value) { console.log(value) })
.catch(function(error) { console.log('报错啦') });
//ok

var promise = new Promise(function(resolve, reject) {
throw new Error('test');
resolve('ok');

});
promise
.then(function(value) { console.log(value) })
.catch(function(error) { console.log('报错啦') });
//报错啦

4、then方法会返回一个promise对象,因此可以继续在then函数后面加then函数,这时前面then函数应该会return一个结果值作为后面then函数的参数,
前面then函数如果执行resolve()则后面then函数也会执行resolve(),前面then函数如果执行reject()则后面then函数也会执行reject(),
但如果前面then函数return一个promise对象,那后面的then函数将会根据这个promise的执行结果去执行resolve()还是reject()

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
var promise = new Promise(function(resolve, reject){
resolve(123)
});
promise.then(function(json) {
console.log('Contents: ' + json);
return 1223;
}, function(error) {
console.error('出错了', error);
return '2423'
})
.then(function(val){
console.log(val);
},function(val){
console.log(val)
});
//"Contents: 123"
//1223

var promise = new Promise(function(resolve, reject){
reject(456)
});
promise.then(function(json) {
console.log('Contents: ' + json);
return 1223;
}, function(val,error) {
console.log(val);
console.error('出错了', error);
return '2423'
})
.then(function(val){
console.log(val);
},function(val){
console.log(val)
});

//456
//"出错了"
//"2423"

var promise = new Promise(function(resolve, reject){
// resolve(123)
reject(456)
});
var promise1 = new Promise(function(resolve, reject){
resolve(789)
});
promise.then(function(json) {
console.log('Contents: ' + json);
return 1223;
}, function(val,error) {
console.log(val);
console.error('出错了', error);
return promise1
})
.then(function(val){
console.log(111+val);
},function(val){
console.log(222+val)
});

//456
//"出错了"
//900

then 方法链式调用箭头函数格式

1
2
3
4
5
6
promise.then(
param1 => {}
).then(
param2 => {},
err => {}
);

5、promise 如果发生了错误,无论reject()或者catch在哪一层,会一直向后传递,直到被捕获为止,即遇到reject()或者catch就会抛出来,如果后续所有回调函数中都没有reject()或者catch,错误就不会被抛出来
catch方法会返回一个promise,所以可以继续写then(),catch()抛完错之后会继续执行后面这个then()
catch()里面还可以再抛错,如果catch后面没有reject()或者catch(),错误将不会被抛出来
如果catch之前的promise没有遇到错误,catch之后又有then(),则执行流程会跳过catch,继续执行catch后面的then

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
var promise = new Promise(function(resolve, reject) {
throw new Error('test');
});
promise
.then(function(value) { console.log(value); return '2'}, function() { //捕获test
throw new Error('test1');
})
.then(function(value) { console.log(value);return '3' })
.catch(function(error) { console.log(error);return '4'}) //捕获test1
.then(function(value) { console.log(value); }) //4


var promise = new Promise(function(resolve, reject) {
resolve('1')
});
promise
.then(function(value) { console.log(value); return '2'}, function() { //1
throw new Error('test1');
})
.then(function(value) { console.log(value);return '3' })//2
.catch(function(error) { console.log(error) return '4'})
.then(function(value) { console.log(value); })//3 没遇到错误直接跳过来

var promise = new Promise(function(resolve, reject) {
throw new Error('test');
});
promise
.then(function(value) { console.log(value); return '2'}, function(error) { //捕获test
console.log(error)
throw new Error('test1');
})
.then(function(value) { console.log(value);return '3' })
.catch(function(error) { console.log(error);throw new Error('test2'); return '4'}) //捕获test1
.then(function(value) { console.log(value); },function(error){//捕获test2
console.log(error)
})

6.promise resolve return err 并不会被catch 或者reject捕获,会将错误以参数形式传给then的resolve回调函数

1
2
3
4
5
6
7
8
9
10
Promise.resolve()
.then(() => {
return new Error('error!!!')
})
.then((res) => {
console.log('then: ', res) //打印出err
})
.catch((err) => {
console.log('catch: ', err)
})

then 或 .catch 返回的值不能是 promise 本身,否则会造成死循环

1
2
3
4
5
6
7
const promise = Promise.resolve()
.then(() => {
return promise
})
promise.catch(console.error)

//抛错

.then 或者 .catch 的参数期望是函数,传入非函数则会发生值穿透

1
2
3
4
Promise.resolve(1)
.then(2)
.then(Promise.resolve(3))
.then(console.log) ///打印1

API

Promise.all

用于将多个 Promise 实例,包装成一个新的 Promise 实例
var p = Promise.all([p1, p2, p3])
p的状态由p1、p2、p3决定,分成两种情况。
(1)只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。

(2)只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。

Promise.race

将多个Promise实例,包装成一个新的Promise实例
var p = Promise.race([p1, p2, p3]);
只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数

Promise.resolve

将现有对象转为Promise对象
var Promise = Promise.resolve(参数);

Promise.reject

返回一个新的 Promise 实例,该实例的状态为rejected。
var p = Promise.reject(‘出错了’);
Promise.reject()方法的参数,会原封不动地作为reject的理由,变成后续方法的参数

done finally

done方法,总是处于回调链的尾端,保证抛出任何可能出现的错误,没有参数
finally方法用于指定不管Promise对象最后状态如何,都会执行的操作
可以接受一个普通的回调函数作为参数,该函数不管怎样都必须执行。

My Little World

对象构造和继承

发表于 2017-08-19

对象特性

对象的属性在定义时,都带有一些特征值,js通过这些特征值定义他们的行为
这些特征值描述对象属性的各种特征,成为对象属性的特性
特性是内部值,放在两对方括号中访问
特性分为数据属性和访问器属性
数据属性:Configurable、Enumerable、Writable、Value
访问器属性:Configurable、Enumerable、Get、Set
定义某个属性的特性:Object.defineProperty(对象名,对象属性名,{特性1:值,特性2:值…})
定义多个属性特性:Object.defineProperties(对象名,{属性1:{特性1:值,特性2:值…},属性2:{特性1:值….}})
读取属性:Object.getOwnPropertyDescriptor(对象名,属性名) 返回一个对象

构造对象

工厂模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function createobj(name,age){
var o=new Object();
o.name = name;
o.age = age;
o.sayName = function(){
console.log(this.name)
};
return o;
}
var person1 = createobj('gray',25);
var person2 = createobj('black',26);
person1.sayName()//gray
person2.sayName()//black
console.log(typeof person1)//object
console.log(person1 instanceof createobj) //false

缺点:无法识别对象类型

寄生构造函数

定义样子同工厂模式,只不过创建实例时用new 操作符
将包装函数叫做 构造函数
实例与构造函数及其原型没有关系

1
2
3
4
5
6
7
8
9
10
11
12
13
function createobj(name,age){  //构造函数
var o=new Object();
o.name = name;
o.age = age;
o.sayName = function(){
console.log(this.name)
};
return o;
}
var person1 = new createobj('gray',25);
person1.sayName()//gray
console.log(person1 instanceof createobj) //false
console.log(person1 instanceof Object) //true

应用:为无法修改构造函数的对象添加方法

1
2
3
4
5
6
7
8
9
10
11
//给数组增加特殊方法
function specialArray(){
var values = new Array();
values.push.apply(values,arguments);
values.toPiedString = function(str){ //特殊方法
return this.join(str);
};
return values;
}
var colors = new specialArray('red','blue');
console.log(colors.toPiedString('|'));//"red|blue"

构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function createobj(name,age){
this.name = name;
this.age = age;
this.sayName = function(){
console.log(this.name)
};
}
var person1 = new createobj('gray',25);
person1.sayName(); //gray

console.log(person1 instanceof createobj)//true
console.log(person1 instanceof Object)//true

console.log(window.name)//''
var person2 = createobj('black',26);
console.log(person2) //undefined
console.log(window.name) //black
console.log(person1.sayName == window.sayName)//false

var person3 = new createobj('orange',25);
console.log(person1.sayName == person3.sayName)//false

console.log(person1.constructor == createobj)//true
console.log(person3.constructor == person1.constructor)//true

1、构造函数的实例均指向构造函数。例:person1、person3的constructor均指向createobj)
2、不使用new 关键字调用构造函数,构造函数的方法和属性会挂到window上面。例:person2
3、定义在构造函数上的方法,创建不同实例后,不同实例各自拥有自己的该方法,不同实例之间构造函数方法不相等,不共享

使用构造函数创建对象,如果相让对象方法在不同实例实现共享,则在定义方法时,采用引用函数的方法

1
2
3
4
5
6
7
8
9
10
11
function createobj(name,age){
this.name = name;
this.age = age;
this.sayName = sayName;
}
function sayName(){
console.log(this.name)
}
var person1 = new createobj('gray',25);
var person3 = new createobj('orange',25);
console.log(person1.sayName == person3.sayName)//true

缺点:若对象需要定义多个方法则需要创建多个全局函数供构造函数引用,失去封装性

原型模式

在对象的prototype中定义的属性和方法,可以在所有实例中共享
定义方法一

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
function createobj(name,age){}
createobj.prototype.name = 'black';
createobj.prototype.age = 26;
createobj.prototype.sayName = function(){
console.log(this.name)
}
var person1 = new createobj();
var person2 = new createobj();
person1.sayName();//black
person2.sayName();//black
console.log(person1.sayName == person2.sayName)//true

console.log(person1.constructor == createobj)//true
console.log(person2.constructor == person1.constructor)//true


console.log(person1.age) //26 来自原型
person1.age = 27; //相当于创建了一个实例属性
console.log(person1.age) //27 来自实例属性
console.log(person2.age) //26 来自原型
delete person1.age; //删除实例属性
console.log(person1.age) //26 来自原型
delete person1.age; //不能删除原型属性
console.log(person1.age) //26
console.log(person2.age) //26

console.log(person1 == createobj.prototype)//false
console.log(person1.prototype == createobj.prototype)//false
console.log(createobj.prototype.isPrototypeOf(person1))//true
console.log(person1.prototype) //undefined
console.log(Object.getPrototypeOf(person1));//createobj.prototype

console.log(person1.hasOwnProperty('name'));//false
console.log('name' in person1);//true
console.log(Object.keys(createobj.prototype));//["name", "age", "sayName"]
console.log(Object.getOwnPropertyNames(createobj.prototype));//["constructor", "name", "age", "sayName"]

obj1
1、实例的内部指针[[Prototype]] 会指向构造函数的原型对象,它是不能直接访问的
2、构造函数的prototype也会指向构造函数的原型对象,但他是构造函数的一个属性,可以直接访问
3、Object.getPrototypeOf(实例名); 返回实例的原型对象 .即[[Prototype]]指向的对象
4、实例名.hasOwnProperty(属性名字符串) ; 只检查实例的实例属性中是否有该属性,没有的话,返回false,可用于检查该属性在实例属性中,还是在原型属性中
5、在获取属性值时,实例属性中有该属性就取该实例属性的值,没有则用原型属性值,原型属性也没有的话则返回undefined
6、实例属性可用delete删除,实例无法更改原型属性的值
7、属性名字符串 in 实例名; 检查实例和原型所有属性中是否有该属性,有的话就返回true;for-in循环可枚举的属性,包括实例属性和原型属性
8、Object.keys(对象名);返回对象可枚举属性的字符串数组
9、Object.getOwnPropertyNames(对象名);返回所有对象的实例属性,无论它是否枚举

定义方法二

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function createobj(){}
createobj.prototype={
name:'seven',
sayName:function(){
console.log(this.name)
}
};
var person1 = new createobj();
person1.sayName();//seven
console.log(person1.constructor == createobj);//false
console.log(person1.constructor == createobj.prototype);//false
console.log(person1.constructor == Object);//true
console.log(person1.constructor == createobj.prototype.constructor);//true
console.log(person1.constructor);//function object(){}

console.log(person1 instanceof Object);//true
console.log(person1 instanceof createobj);//true

1、将挂在构造函数原型上的属性以一个对象的形式,一起赋给构造函数原型,即构造函数原型被赋值为一个对象
2、构造函数原型被Object创建,实例、构造函数的原型对象的构造函数为Object对象
3、若在定义原型对象时,指定原型对象的constructor,则对象实例的constructor也会跟着指到相应的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function createobj(){}
createobj.prototype={
constructor:createobj, //明确指出,同时特性的枚举属性会被设置为true,默认为false
name:'seven',
sayName:function(){
console.log(this.name)
}
};
var person1 = new createobj();
person1.sayName();//seven
console.log(person1.constructor == createobj);//true
console.log(person1.constructor == createobj.prototype);//false
console.log(person1.constructor == Object);//false
console.log(person1.constructor == createobj.prototype.constructor);//true
console.log(person1.constructor);//function createobj(){}

console.log(person1 instanceof Object);//true
console.log(person1 instanceof createobj);//true

4、 实例中的指针指向原型,不指向构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function createobj(){}

var person1 = new createobj();
console.log(person1 instanceof createobj);//true
createobj.prototype={
constructor:createobj,
name:'seven',
sayName:function(){
console.log(this.name)
}
};
//person1.sayName();//person1.sayName is not a function
console.log(person1.constructor == createobj);//true
console.log(person1.constructor == createobj.prototype);//false
console.log(person1.constructor == Object);//false
console.log(person1.constructor == createobj.prototype.constructor);//true
console.log(person1.constructor);//function createobj(){}

console.log(person1 instanceof Object);//true
console.log(person1 instanceof createobj);//false

obj2
对象生成实例后,再去重写构造函数的原型对象,会将构造函数的原型对象指向后来被赋的对象,
切断与旧的原型对象之间的关系,新的原型对象与实例没有任何关系,实例仍引用旧的原型对象

原型方法可用于修改添加原生对象的属性方法
缺点:在原型对象prototype中的属性方法全都共享,当更改一个实例中的属性值时,其他实例一起变

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
//定义一
function createobj(name,age){}
createobj.prototype.name = [1,2];
createobj.prototype.sayName = function(){
console.log(this.name)
}
var person1 = new createobj();
var person2 = new createobj();
person1.sayName();//[1, 2]
person2.sayName();//[1, 2]
person1.name.push(3);
person1.sayName();//[1, 2, 3]
person2.sayName();//[1, 2, 3]
//定义二
function createobj(){}
createobj.prototype={
constructor:createobj,
name:[1,2],
sayName:function(){
console.log(this.name)
}
};
var person1 = new createobj();
var person2 = new createobj();
console.log(person1.name)//[1, 2]
console.log(person2.name)//[1, 2]
person1.name.push(3);
console.log(person1.name)//[1, 2, 3]
console.log(person2.name)//[1, 2, 3]

构造函数+原型模式

构造函数定义实例属性,
原型模式定义方法和共享属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function createobj(name,age){
this.name = name;
this.age = age;
this.friends = [1,2];
}
createobj.prototype={
constructor:createobj,
sayName:function(){
console.log(this.name)
}
};
var person1 = new createobj('halo',24);
var person2 = new createobj('jack',25);
person1.friends.push(3);
console.log(person1.friends);//[1, 2, 3]
console.log(person2.friends);//[1, 2]

是用来定义引用类型的一种默认模式

动态原型模式

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
function createobj(name,age){
this.name = name;
this.age = age;
if(typeof this.sayName !='function'){
createobj.prototype.sayName = function(){
console.log(this.name)
}
}
}
var person1 = new createobj('halo',24);
var person2 = new createobj('jack',26);
person1.sayName();//halo
person2.sayName();//jack

//修改原型
createobj.prototype.sayName = function(){
console.log('111')
}
person1.sayName();//111
person2.sayName();//111

//使用字面方式重写
createobj.prototype={
constructor:createobj,
sayName:function(){
console.log(222)
}
};
var person3 = new createobj('kitty',24);
person1.sayName();//111
person2.sayName();//111
person3.sayName();//222
console.log(person1.prototype);//undefined

//重写一个原型方法
person1.sayName=function(){
console.log('333');
}
person1.sayName();//333
person2.sayName();//111

1、判断sayName函数是否存在的语句,只会在初次调用构造函数的时候运行,即第一次创建实例的时候运行,
运行过后,构造函数原型中就会存在sayName函数,即完成初始化,之后就不会在运行
2、对原型模式定义的方法属性能够在所有实例中立即得到放映
3、如果对构造函数原型使用字面方式重写,将切断已有实例与构造函数原型的联系,
已有实例会指向就原型对象,新建实例会指向新原型对象
4、在一个实例中重写一个原型方法,不会影响原型对象方法,其他实例和新建实例仍会调用原型对象方法

稳妥构造函数

创建对象的实例方法,不使用this
不使用new调用构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function createobj(name,age){  //构造函数
var o=new Object();
o.name = name;
o.age = age;
o.sayName = function(){
console.log(this.name);
console.log(this == o)
};
return o;
}
var person1 = createobj('gray',25);
person1.sayName()//gray true

console.log(person1 instanceof createobj) //false

只用通过sayName函数访问传入到构造函数中的原始成员

继承

实例原型链模式

继承函数的原型被赋值为祖先函数的实例

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
//祖先函数
function Superfn(){
this.pro = true;
this.color = [1,2];
this.getsuper = function(){
console.log('aaa');
}
}
Superfn.prototype.getsuper=function(){
console.log('bbb');
}
//继承函数
function Subfn(){
this.subpro = false;
this.getsuper = function(){
console.log('000');
}
}
Subfn.prototype = new Superfn();
Subfn.prototype.getsuper = function(){
console.log('ccc')
}
var ins = new Subfn();
ins.getsuper();

console.log(ins instanceof Object); //true
console.log(ins instanceof Superfn); //true
console.log(ins instanceof Subfn); //true
console.log(Object.prototype.isPrototypeOf(ins)); //true
console.log(Superfn.prototype.isPrototypeOf(ins)); //true
console.log(Subfn.prototype.isPrototypeOf(ins)); //true

var ins1 = new Subfn();
console.log(ins.color)//[1,2]
console.log(ins1.color)//[1,2]
ins1.color.push(4);
console.log(ins.color)//[1,2,4]
console.log(ins1.color)//[1,2,4]

1.继承函数实例的属性/方法调用顺序:
自己定义的->继承函数实例->继承函数prototype->祖先函数实例属性->祖先函数prototype
2.因为所有引用类型默认继承object,所以调用toString等方法时,其实调用的的是object的prototype
3.重写原型中祖先函数的方法,一定要在继承函数原型被赋值祖先函数实例之后,相当于用实例方法覆盖原型方法
但注意不能用字面量方法重写继承函数原型,这样会导致继承函数原型重新指向新对象,切断与祖先函数联系
4.因为继承函数prototype指向祖先函数实例时,祖先函数所有属性相当于继承函数prototype属性,
构造函数原型的属性会被所有实例共享,所以创建继承函数实例时,
不能像祖先函数的构造函数中传递参数,因为会导致其他实例同时共享这些参数导致的后果

借用构造函数

使用apply()或者call()方法。在继承函数构造函数中调用祖先函数构造函数,这样,
在new 一个继承函数实例时,就会去执行祖先函数中所有对象初始化代码,每个实例都会具有自己的祖先函数属性的副本

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
function Superfn(time){
this.color = [1,2];
this.time = time;
}

Superfn.prototype.getsuper=function(){
console.log('bbb');
}

function Subfn(){
Superfn.call(this,'11:23:30');
this.age = 29;
}

var ins = new Subfn();
var ins1 = new Subfn();
console.log(ins.color) //[1, 2]
console.log(ins1.color)//[1, 2]
ins1.color.push(4);
console.log(ins.color)//[1, 2]
console.log(ins1.color)//[1, 2, 4]

console.log(ins.time);//"11:23:30"
console.log(ins.age);//29

ins.getsuper();//ins.getsuper is not a function

1、可以像祖先函数构造函数中传递参数;
2、定义继承函数自己的属性必须放在调用祖先函数之后,防止祖先函数重写继承函数属性
缺点: 所有的属性只能使用构造函数模式定义,而且,继承函数实例,不能调用祖先函数原型上的方法

组合继承

在继承函数构造函数中调用祖先函数,实现属性继承
将继承函数的原型赋值为祖先函数实例,实现方法复制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function Superfn(time){
this.color = [1,2];
this.time = time;
}
Superfn.prototype.getsuper=function(){
console.log('bbb');
}
function Subfn(){
Superfn.call(this,'11:23:30');
this.age = 29;
}
Subfn.prototype= new Superfn();
var ins = new Subfn();
var ins1 = new Subfn();
console.log(ins.color)
console.log(ins1.color)
ins1.color.push(4);
console.log(ins.color)
console.log(ins1.color)

console.log(ins.time);//"11:23:30"
console.log(ins.age);//29

ins.getsuper();//bbb

缺点:调用了两次Superfn(),初始化时,Subfn.prototype中的Superfn的实例属性会被构造函数中创建的Superfn的实例属性覆盖,
重复创建,没必要

原型式继承

Object.creat()方法原理像如下函数
function object(o){
function F(){
F.prototype = o;
}
return new F();
}
原型式继承 即使用Object.creat(祖先对象)定义实例,实例会共享祖先对象属性方法,通过添加第二个参数,覆盖祖先对象属性,或新增属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Superfn={
color:['red','green'],
getsuper:function(){
console.log(222);
}
}

var test = Object.create(Superfn,{getsuper:{value:function(){console.log(111)}}});
var test1 = Object.create(Superfn,{color:{value:[1,2]},name:{value:'jack'}});
console.log(test.color);//['red','green']
console.log(test1.color,test1.name); //[1,2] jack
console.log(test.name);//undefined
test.getsuper(); //111
test1.getsuper();//222

寄生式继承

1
2
3
4
5
6
7
8
9
10
11
12
13
function createAnthoer(oriobj){
var clone = Object(oriobj);//创建oriobj对象副本
clone.sayHi=function(){//以某种方式增强这个对象,例如定义自己的方法
console.log('hi')
}
return clone;
}
var Superfn={
color:['red','green'],
}
var test = createAnthoer(Superfn);
test.sayHi()//hi
console.log(test.color);//["red", "green"]

最理想继承-寄生组合式继承

对比组合式继承
仅调用一次superfn函数,避免在Subfn.prototype中创建多余superfn实例属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function inhertPrototype(subfn,superfn){
var prototype = Object(superfn.prototype);//创建对象 ,仅继承祖先函数原型
prototype.constructor = subfn;//增强对象
subfn.prototype = prototype;//指定对象
}
function Superfn(name){
this.color = [1,2];
this.name = name;
}
Superfn.prototype.getsuper=function(){
console.log('bbb');
}
function Subfn(name,age){
Superfn.call(this,name); //仅继承祖先函数实例属性,不会继承祖先函数原型
this.age = age;
}
Subfn.prototype.getsuper=function(){
console.log('ccc');
}
inhertPrototype(subfn,superfn);

类

类的定义相当于es5 对象构造函数的另一种写法,一种新语法
es5 的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面
es6 类的继承,实质是先创造父类的实例对象this(必须先调用super方法),然后再用子类的构造函数修改this

类的定义

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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
//es5
function Point(x, y) {
this.x = x;
this.y = y;
}

Point.prototype.toString = function () {
return '(' + this.x + ', ' + this.y + ')';
};

var p = new Point(1, 2);
//es6
let methodName = 'getArea';
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
this.toString = function(){
console.log(111);
}
}

toString() {
return '(' + this.x + ', ' + this.y + ')';
}

[methodName]() {
console.log(666);
}
}

var temp = new Point('helo','world');
console.log(temp)// constructor定义的内容
console.log(temp.toString()) //先找实例属性,在去原型找属性

console.log(typeof Point) // "function"
console.log(Point === Point.prototype.constructor) // true

console.log(Object.keys(Point.prototype));//[]
console.log(Object.getOwnPropertyNames(Point.prototype));//["constructor", "toString"]
Object.assign(Point.prototype, {
toString222(){console.log(222);},
toValue(){console.log(333);}
});
temp.toString222()
console.log(Object.keys(Point.prototype));//["toString222", "toValue"]
console.log(Object.getOwnPropertyNames(Point.prototype));//["constructor", "toString", "toString222", "toValue"]


temp[methodName]() //666

class Foo {
constructor() {
return Object.create(null);
}
}
console.log(Foo.name)//Foo
console.log(new Foo() instanceof Foo)//false
var tt = new Foo();
console.log(tt.__proto__ == Point) //TRUE

var p1 = new Point(2,3);
var p2 = new Point(3,2);
console.log(p1.__proto__ === p2.__proto__)//true
p1.__proto__.printName = function () { console.log('Oops') };
p1.printName() // "Oops"
p2.printName() // "Oops"
var p3 = new Point(4,2);
p3.printName() // "Oops

//这个类的名字是MyClass而不是Me,Me只在 Class 的内部代码可用,指代当前类。
const MyClass = class Me {
getClassName() {
console.log(Me.name);
}
};
let inst = new MyClass();
inst.getClassName() // Me
console.log(MyClass.name)//me

//立即执行的 Class
let person = new class {
constructor(name) {
this.name = name;
}

sayName() {
console.log(this.name);
}
}('张三');

person.sayName(); // "张三"

class MyClass {
constructor() {
// ...
}
get prop() {
return 'getter';
}
set prop(value) {
console.log('setter: '+value);
s}
let inst = new MyClass();
inst.prop = 123;// setter: 123
console.log(inst.prop)// 'getter'

1.constructor函数定义实例属性,this代表实例对象,生成对象实例时,自动调用该方法,
默认返回实例对象this,也可以指定返回另外一个对象,则生成的实例将是另外这个对象的实例
如果没有显式定义,一个空的constructor方法会被默认添加。
其他在constructor函数之外定义的函数全都挂在类的prototype对象上(没有定义在this上的全都定义在class上)
其实constructor函数也挂在prototype对象上
2.定义原型方法时,不需要加上function这个关键字,方法之间不需要逗号分隔
3.类的数据类型就是函数,类本身就指向构造函数
4.使用Object.assign()新增原型方法,定义类时定义的原型方法是不可枚举的,但Object.assign()新增的可枚举
5.可以使用变量值做原型方法名
6.定义实例仍使用new关键字,且必须使用new定义,否则会报错
7.类的所有实例共享一个原型对象,使用实例的proto属性改写原型,会改变“类”的原始定义,影响到所有实例
8.类也可以使用表达式的形式定义,采用 Class 表达式,可以写出立即执行的 Class
9.类必须先定义再使用,无论是生成实例还是进行继承
10.类的name属性总是返回紧跟在class关键字后面的类名。
11.对某个属性设置对应的存值函数和取值函数,因此赋值和读取行为都被自定义了。存值函数和取值函数是设置在属性的 Descriptor 对象上的。
12.静态方法,该方法不会被实例继承,而是直接通过类来调用
如果静态方法包含this关键字,这个this指的是类,而不是实例
父类的静态方法,可以被子类继承。
静态方法也是可以从super对象上调用的
13.
ES6 为new命令引入了一个new.target属性,该属性一般用在构造函数之中,返回new命令作用于的那个构造函数。如果构造函数不是通过new命令调用的,new.target会返回undefined,因此这个属性可以用来确定构造函数是怎么调用的。

类的继承

定义

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
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
console.log(new.target.name);
}
}

class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y);
this.color = color;
}
}

let cp0 = new Point(25, 8);//"Point"
let cp = new ColorPoint(25, 8, 'green');//"ColorPoint" super()使用

console.log(cp instanceof ColorPoint)//true
console.log(cp instanceof Point)//true
console.log(Object.getPrototypeOf(ColorPoint) === Point)

console.log(ColorPoint.__proto__ === Point) // true
console.log(ColorPoint.prototype.__proto__ === Point.prototype) // true

console.log(cp.__proto__.__proto__ === cp0.__proto__ )// true
cp.__proto__.__proto__.printName = function () {
console.log('Ha');
};

cp0.printName() // "Ha"

1、使用extends关键字进行继承:class 子类名 extends 父类名{}
2、在子类构造函数constructor中使用super关键字继承父类this对象,必需在super语句之后使用this
3、Object.getPrototypeOf方法可以用来从子类上获取父类
4、子类的proto属性,表示构造函数的继承,总是指向父类。
子类prototype属性的proto属性,表示方法的继承,总是指向父类的prototype属性。
5、子类的原型的原型,是父类的原型,通过子类实例的proto.proto属性,可以修改父类实例的行为

关于super关键字

1、用作函数
作为函数时,super()只能用在子类的构造函数之中,用在其他地方就会报错,代表调用父类构造函数
super()在这里相当于parent.prototype.constructor.call(this),this指子类

2、用作对象

作为对象使用时,在普通方法中指向父类原型对象

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
class A {
constructor() {
this.p = 2;
}
q() {
console.log(this.p)
}
}

class B extends A {
constructor() {
super();
this.p =3
super.q();
super.p = 5;
console.log(super.p); // undefined
console.log(this.p); // 5
super.tt = 6;
console.log(this.tt); // 6
console.log(super.valueOf() instanceof B); // true
}
get m() {
return super.p;
}
}

let b = new B();//3
console.log(b.m) // undefined

在普通方法中指向父类原型对象,即使用super.xxxx(),相当于使用parent.prototype.xxxx(),
所以只能调用父类原型属性方法,不能使用实例属性方法

通过super调用父类的方法时,super会绑定子类的this,
即在子类使用super.xxxx()时,xxxx()里面的this指子类
通过super对某个属性赋值,赋值的属性会变成子类实例的属性

在静态方法中使用super将指向父类,而不是父类的原型对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Parent {
static myMethod(msg) {
console.log('static', msg);
}

myMethod(msg) {
console.log('instance', msg);
}
}

class Child extends Parent {
static myMethod(msg) {
super.myMethod(msg);
}

myMethod(msg) {
super.myMethod(msg);
}
}

Child.myMethod(1); // static 1 super在静态方法之中指向父类

var child = new Child();
child.myMethod(2); // instance 2 在普通方法之中指向父类的原型对象。

extends特殊对象

第一种特殊情况,子类继承Object类。

1
2
3
4
5
class A extends Object {
}

A.__proto__ === Object // true
A.prototype.__proto__ === Object.prototype // true

这种情况下,A其实就是构造函数Object的复制,A的实例就是Object的实例。

第二种特殊情况,不存在任何继承。

1
2
3
4
5
class A {
}

A.__proto__ === Function.prototype // true
A.prototype.__proto__ === Object.prototype // true

这种情况下,A作为一个基类(即不存在任何继承),就是一个普通函数,所以直接继承Function.prototype。但是,A调用后返回一个空对象(即Object实例),所以A.prototype.proto指向构造函数(Object)的prototype属性。

第三种特殊情况,子类继承null。

1
2
3
4
5
class A extends null {
}

A.__proto__ === Function.prototype // true
A.prototype.__proto__ === undefined // true

这种情况与第二种情况非常像。A也是一个普通函数,所以直接继承Function.prototype。但是,A调用后返回的对象不继承任何方法,所以它的proto指向Function.prototype,即实质上执行了下面的代码。

1
2
3
class C extends null {
constructor() { return Object.create(null); }
}

第四种情况,允许继承原生构造函数定义子类,但无法通过super方法向父类Object传参

1
2
3
4
5
6
7
class NewObj extends Object{
constructor(){
super(...arguments);
}
}
var o = new NewObj({attr: true});
console.log(o.attr === true) // false

Mixin 模式

使用如下mix函数将多个对象合成为一个类

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
function mix(...mixins) {
class Mix {}

for (let mixin of mixins) {
copyProperties(Mix, mixin);
copyProperties(Mix.prototype, mixin.prototype);
}

return Mix;
}

function copyProperties(target, source) {
for (let key of Reflect.ownKeys(source)) {
if ( key !== "constructor"
&& key !== "prototype"
&& key !== "name"
) {
let desc = Object.getOwnPropertyDescriptor(source, key);
Object.defineProperty(target, key, desc);
}
}
}

//使用:继承返回的合成类
class DistributedEdit extends mix(Loggable, Serializable) {
// ...
}

几种创建方式对比

let a = null ===> null
let b = Object.create(null) ===> {} 但没有任何属性
let c = {} ===> {} proto指向Object
let d = new Object() ===> {} proto指向Object
let e = new Object(null) ===> {} proto指向Object
let f = Object.create(c) ===> {} proto指向c, proto.proto指向Object
!(obj4)[/image/ob4.png]
c instanceof Object , d instanceof Object ,e instanceof Object ===> true

let dd = {a:1}
let cc = new Object(dd)
let ee = Object.create(dd)
console.log(cc,ee)
!(obj3)[/image/ob3.png]
dd.isPrototypeOf(cc) ===>false
dd.isPrototypeOf(ee) ===>true

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
}

应用

1…161718…26
YooHannah

YooHannah

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