浅谈web前端安全机制问题(转)
前言
“安全”是个很大的话题,各种安全问题的类型也是种类繁多。如果我们把安全问题按照所发生的区域来进行分类的话,那么所有发生在后端服务器、应用、服务当中的安全问题就是“后端安全问题”,所有发生在浏览器、单页面应用、Web页面当中的安全问题则算是“前端安全问题”。
XSS
XSS是跨站脚本攻击(Cross-Site Scripting)的简称
XSS分为:存储型和反射型
存储型
存储型XSS:存储型XSS,持久化,代码是存储在服务器中的,如在个人信息或发表文章等地方,加入代码,如果没有过滤或过滤不严,那么这些代码将储存到服务器中,用户访问该页面的时候触发代码执行。这种XSS比较危险,容易造成蠕虫,盗窃cookie(虽然还有种DOM型XSS,但是也还是包括在存储型XSS内)。
反射型
反射型XSS:非持久化,需要欺骗用户自己去点击链接才能触发XSS代码(服务器中没有这样的页面和内容),一般容易出现在搜索页面。
如何防御
防御XSS最佳的做法就是对数据进行严格的输出编码,使得攻击者提供的数据不再被浏览器认为是脚本而被误执行。1
例如<script>在进行HTML编码后变成了<script>
而这段数据就会被浏览器认为只是一段普通的字符串,而不会被当做脚本执行了。
你可以查阅OWASP XSS Prevention Cheat Sheet,里面有关于XSS及其防御措施的详细说明。
iframe带来的风险
风险
有些时候我们的前端页面需要用到第三方提供的页面组件,通常会以iframe的方式引入。典型的例子是使用iframe在页面上添加第三方提供的广告、天气预报、社交分享插件等等。
iframe在给我们的页面带来更多丰富的内容和能力的同时,也带来了不少的安全隐患。因为iframe中的内容是由第三方来提供的,默认情况下他们不受我们的控制,他们可以在iframe中运行JavaScirpt脚本、Flash插件、弹出对话框等等,这可能会破坏前端用户体验。
如果说iframe只是有可能会给用户体验带来影响,看似风险不大,那么如果iframe中的域名因为过期而被恶意攻击者抢注,或者第三方被黑客攻破,iframe中的内容被替换掉了,从而利用用户浏览器中的安全漏洞下载安装木马、恶意勒索软件等等,这问题可就大了。
如何防御
还好在HTML5中,iframe有了一个叫做sandbox的安全属性,通过它可以对iframe的行为进行各种限制,充分实现“最小权限“原则。使用sandbox的最简单的方式就是只在iframe元素中添加上这个关键词就好,就像下面这样:1
<iframe sandbox src="..."> ... </iframe>
sandbox还忠实的实现了“Secure By Default”原则,也就是说,如果你只是添加上这个属性而保持属性值为空,那么浏览器将会对iframe实施史上最严厉的调控限制,基本上来讲就是除了允许显示静态资源以外,其他什么都做不了。比如不准提交表单、不准弹窗、不准执行脚本等等,连Origin都会被强制重新分配一个唯一的值,换句话讲就是iframe中的页面访问它自己的服务器都会被算作跨域请求。
另外,sandbox也提供了丰富的配置参数,我们可以进行较为细粒度的控制。一些典型的参数如下:1
2
3
4allow-forms:允许iframe中提交form表单
allow-popups:允许iframe中弹出新的窗口或者标签页(例如,window.open(),showModalDialog(),target=”_blank”等等)
allow-scripts:允许iframe中执行JavaScript
allow-same-origin:允许iframe中的网页开启同源策略
更多详细的资料,可以参考iframe中关于sandbox的介绍
请求劫持与HTTPS
请求劫持
DNS劫持
DNS劫持就是通过劫持了DNS服务器,通过某些手段取得某域名的解析记录控制权,进而修改此域名的解析结果,导致对该域名的访问由原IP地址转入到修改后的指定IP,其结果就是对特定的网址不能访问或访问的是假网址,从而实现窃取资料或者破坏原有正常服务的目的。DNS劫持通过篡改DNS服务器上的数据返回给用户一个错误的查询结果来实现的。 DNS劫持症状:在某些地区的用户在成功连接宽带后,首次打开任何页面都指向ISP提供的“电信互联星空”、“网通黄页广告”等内容页面。还有就是曾经出现过用户访问Google域名的时候出现了百度的网站。这些都属于DNS劫持。 再说简单点,当你输入google.com这个网址的时候,你看到的网站却是百度的首页。
http劫持:
在用户的客户端与其要访问的服务器经过网络协议协调后,二者之间建立了一条专用的数据通道,用户端程序在系统中开放指定网络端口用于接收数据报文,服务器端将全部数据按指定网络协议规则进行分解打包,形成连续数据报文。 用户端接收到全部报文后,按照协议标准来解包组合获得完整的网络数据。其中传输过程中的每一个数据包都有特定的标签,表示其来源、携带的数据属性以及要到何处,所有的数据包经过网络路径中ISP的路由器传输接力后,最终到达目的地,也就是客户端。 HTTP劫持是在使用者与其目的网络服务所建立的专用数据通道中,监视特定数据信息,提示当满足设定的条件时,就会在正常的数据流中插入精心设计的网络数据报文,目的是让用户端程序解释“错误”的数据,并以弹出新窗口的形式在使用者界面展示宣传性广告或者直接显示某网站的内容。列入本地的fiddler为一种劫持
请求劫持唯一可行的预防方法就是尽量使用HTTPS协议访问。
公钥和私钥
什么是https,这里不再解释了,简单理解就是通过SSL(Secure Sockets Layer)层来加密http数据来进行安全传输。 那使用HTTPS是怎样进行安全数据传输的?
先看个有意思的问题:
A、B两个人分别在两个岛上,并且分别有一个箱子,一把锁,和打开这把锁的钥匙(A的钥匙打不开B手上的锁,B的钥匙也打不开A的锁)。此时A要跟B互通情报,此时需要借助C的船运输,C是一个不可靠的人,如果A直接把情报送给B或把情报放在箱子里给B,都可能会被C偷走;如果A把情报锁在箱子里,B没有打开A锁的钥匙无法获得情报内容。请问有什么办法可以尽可能快的让A和B互通情报。
这就是公钥和私钥的问题了,答案比较简单,也对应了公钥和私钥在https中的应用过程。
公钥(Public Key)与私钥(Private Key)是通过一种算法得到的一个密钥对(即一个公钥和一个私钥),公钥是密钥对中公开的部分,私钥则是非公开的部分。公钥通常用于加密会话密钥、验证数字签名,或加密可以用相应的私钥解密的数据。通过这种算法得到的密钥对能保证在世界范围内是唯一的。使用这个密钥对的时候,如果用其中一个密钥加密一段数据,必须用另一个密钥解密。比如用公钥加密数据就必须用私钥解密,如果用私钥加密也必须用公钥解密,否则解密将不会成功。
Https的通信过程
1、客户端发送https请求,告诉服务器发将建立https连接
2、服务器将服务端生成的公钥返回给客户端,如果是第一次请求将告诉客户端需要验证链接
3、客户端接收到请求后’client finished’报文串通过获取到的服务器公钥加密发送给服务器,并将客户端生成的公钥也发送给服务器
4、服务器获取到加密的报文和客户端公钥,先使用服务器私钥解密报文,然后将报文通过客户端的公钥加密返回给客户端。
5、客户端通过私钥解密报文,判断是否为自己开始发送的报文串;如果正确,说明安全连接验证成功,将数据通过服务器公钥加密不断发送给服务器,服务器也不断解密获取报文,并通过客户端公钥加密返回给客户端验证。这样就建立了不断通信的连接。
Https协议头解析
以打开 https://github.com/ 的过程为例,请求通用头部如下
Request URL:https://github.com/ouvens\n
Request Method:GET
Status Code:200 OK (from cache)
Remote Address:192.30.252.131:443
Response Headers
其它浏览器web安全控制
X-XSS-Protection
这个header主要是用来防止浏览器中的反射性xss。现在,只有IE,chrome和safari(webkit)支持这个header。
正确的设置:
X-XSS-Protection:1; mode=block
0 – 关闭对浏览器的xss防护
1 – 开启xss防护
1; mode=block – 开启xss防护并通知浏览器阻止而不是过滤用户注入的脚本。
1; report=http://site.com/report – 这个只有chrome和webkit内核的浏览器支持,这种模式告诉浏览器当发现疑似xss攻击的时候就将这部分数据post到指定地址。 通常不正确的设置
X-Content-Type-Options
&ems; 这个header主要用来防止在IE9、chrome和safari中的MIME类型混淆攻击。firefox目前对此还存在争议。通常浏览器可以通过嗅探内容本身的方法来决定它是什么类型,而不是看响应中的content-type值。通过设置 X-Content-Type-Options:如果content-type和期望的类型匹配,则不需要嗅探,只能从外部加载确定类型的资源。举个例子,如果加载了一个样式表,那么资源的MIME类型只能是text/css,对于IE中的脚本资源,以下的内容类型是有效的:
application/ecmascript
application/javascript
application/x-javascript
text/ecmascript
text/javascript
text/jscript
text/x-javascript
text/vbs
text/vbscript
对于chrome,则支持下面的MIME 类型:
text/javascript
text/ecmascript
application/javascript
application/ecmascript
application/x-javascript
text/javascript1.1
text/javascript1.2
text/javascript1.3
text/jscript
text/live script
nosniff – 这个是唯一正确的设置,必须这样。
Strict-Transport-Security
Strict Transport Security (STS) 是用来配置浏览器和服务器之间安全的通信。它主要是用来防止中间人攻击,因为它强制所有的通信都走TLS。目前IE还不支持 STS头。需要注意的是,在普通的http请求中配置STS是没有作用的,因为攻击者很容易就能更改这些值。为了防止这样的现象发生,很多浏览器内置了一个配置了STS的站点list。
正确的设置 : 注意下面的值必须在https中才有效,如果是在http中配置会没有效果。
max-age=31536000 – 告诉浏览器将域名缓存到STS list里面,时间是一年。
max-age=31536000; includeSubDomains – 告诉浏览器将域名缓存到STS list里面并且包含所有的子域名,时间是一年。
max-age=0 – 告诉浏览器移除在STS缓存里的域名,或者不保存此域名。
通常不正确的设置
判断一个主机是否在你的STS缓存中,chrome可以通过访问chrome://net-internals/#hsts,首先,通过域名请求选项来确认此域名是否在你的STS缓存中。然后,通过https访问这个网站,尝试再次请求返回的STS头,来决定是否添加正确。
.Content-Security-Policy
CSP是一种由开发者定义的安全性政策性申明,通过CSP所约束的的规责指定可信的内容来源(这里的内容可以指脚本、图片、iframe、fton、style等等可能的远程的资源)。通过CSP协定,让WEB能够加载指定安全域名下的资源文件,保证运行时处于一个安全的运行环境中。
正确配置:
Content-Security-Policy:default-src ; base-uri ‘self’; block-all-mixed-content; child-src ‘self’ render.githubusercontent.com; connect-src ‘self’ uploads.github.com status.github.com api.github.com www.google-analytics.com github-cloud.s3.amazonaws.com wss://live.github.com; font-src assets-cdn.github.com; form-action ‘self’ github.com gist.github.com; frame-src ‘self’ render.githubusercontent.com; img-src ‘self’ data: assets-cdn.github.com identicons.github.com www.google-analytics.com collector.githubapp.com .gravatar.com .wp.com .githubusercontent.com; media-src ‘none’; object-src assets-cdn.github.com; plugin-types application/x-shockwave-flash; script-src assets-cdn.github.com; style-src ‘self’ ‘unsafe-inline’ assets-cdn.github.com
X-Frame-Options
这个header主要用来配置哪些网站可以通过frame来加载资源。它主要是用来防止UI redressing 补偿样式攻击。IE8和firefox 18以后的版本都开始支持ALLOW-FROM。chrome和safari都不支持ALLOW-FROM,但是WebKit已经在研究这个了。
正确的设置
X-Frame-Options: deny
deny – 禁止所有的资源(本地或远程)试图通过frame来加载其他也支持X-Frame-Options 的资源。
sameorigion – 只允许遵守同源策略的资源(和站点同源)通过frame加载那些受保护的资源。
allow-from http://www.example.com – 允许指定的资源(必须带上协议http或者https)通过frame来加载受保护的资源。这个配置只在IE和firefox下面有效。其他浏览器则默认允许任何源的资源(在X-Frame-Options没设置的情况下)。
Access-Control-Allow-Origin
Access-Control-Allow-Origin是从Cross Origin Resource Sharing (CORS)中分离出来的。这个header是决定哪些网站可以访问资源,通过定义一个通配符来决定是单一的网站还是所有网站可以访问我们的资源。需要注意的是,如果定义了通配符,那么 Access-Control-Allow-Credentials选项就无效了,而且user-agent的cookies不会在请求里发送。
正确的设置
Access-Control-Allow-Origin :
– 通配符允许任何远程资源来访问含有Access-Control-Allow-Origin 的内容。
http://www.example.com – 只允许特定站点才能访问(http://[host], 或者 https://[host])
Public-Key-Pins
公钥固定(Public Key Pinning)是指一个证书链中必须包含一个白名单中的公钥,也就是说只有被列入白名单的证书签发机构(CA)才能为某个域名*.example.com签发证书,而不是你的浏览器中所存储的任何 CA 都可以为之签发。可以理解为https的证书域名白名单。 Public-Key-Pins (PKP)的目的主要是允许网站经营者提供一个哈希过的公共密钥存储在用户的浏览器缓存里。跟Strict-Transport-Security功能相似的是,它能保护用户免遭中间人攻击。这个header可能包含多层的哈希运算,比如pin-sha256=base64(sha256(SPKI)),具体是先将 X.509 证书下的Subject Public Key Info (SPKI) 做sha256哈希运算,然后再做base64编码。然而,这些规定有可能更改,例如有人指出,在引号中封装哈希是无效的,而且在33版本的chrome中也不会保存pkp的哈希到缓存中。
这个header和 STS的作用很像,因为它规定了最大子域名的数量。此外,pkp还提供了一个Public-Key-Pins-Report-Only 头用来报告异常,但是不会强制阻塞证书信息。当然,这些chrome都是不支持的。
参考
https://raymii.org/s/articles/HTTP_Public_Key_Pinning_Extension_HPKP.html
https://www.veracode.com/blog/2014/03/guidelines-for-setting-security-headers/
原文链接 http://jixianqianduan.com/frontend-weboptimize/2016/03/20/web-security-and-https.html
vuex理解学习总结
什么是Vuex?
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
状态管理模式、集中式存储管理 一听就很高大上,蛮吓人的。在我看来 vuex 就是把需要共享的变量全部存储在一个对象里面,然后将这个对象放在顶层组件中供其他组件使用。这么说吧,将vue想作是一个js文件、组件是函数,那么vuex就是一个全局变量,只是这个“全局变量”包含了一些特定的规则而已。
在vue的组件化开发中,经常会遇到需要将当前组件的状态传递给其他组件。父子组件通信时,我们通常会采用 props + emit 这种方式。但当通信双方不是父子组件甚至压根不存在相关联系,或者一个状态需要共享给多个组件时,就会非常麻烦,数据也会相当难维护,这对我们开发来讲就很不友好。vuex 这个时候就很实用,不过在使用vuex之后也带来了更多的概念和框架,需慎重!
使用方法
直接下载/CDN引用
1 | <script src="https://cdn.jsdelivr.net/npm/vue"></script> |
npm
1 | npm install vuex --save |
然后在入口文件引入方式如下:1
2
3
4import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
store
每一个 Vuex 应用的核心就是 store(仓库)。“store”基本上就是一个容器,它包含着你的应用中大部分的状态 (state)。Vuex 和单纯的全局对象有以下两点不同:1
2Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
你不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。
创建一个 store1
2
3
4
5
6
7
8
9
10
11
12// 如果在模块化构建系统中,请确保在开头调用了 Vue.use(Vuex)
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
}
})
现在,你可以通过 store.state 来获取状态对象,以及通过 store.commit 方法触发状态变更:1
2store.commit('increment')
console.log(store.state.count) // -> 1
再次强调,我们通过提交 mutation 的方式,而非直接改变 store.state.count,是因为我们想要更明确地追踪到状态的变化。这个简单的约定能够让你的意图更加明显,这样你在阅读代码的时候能更容易地解读应用内部的状态改变。此外,这样也让我们有机会去实现一些能记录每次状态改变,保存状态快照的调试工具。有了它,我们甚至可以实现如时间穿梭般的调试体验。
vuex里面有哪些内容?
1 | const store = new Vuex.Store({ |
vuex 包含有五个基本的对象1
2
3
4
5state:存储状态。也就是变量;
getters:派生状态。也就是set、get中的get,有两个可选参数:state、getters分别可以获取state中的变量和其他的getters。外部调用方式:store.getters.personInfo()。就和vue的computed差不多;
mutations:提交状态修改。也就是set、get中的set,这是vuex中唯一修改state的方式,但不支持异步操作。第一个参数默认是state。外部调用方式:store.commit('SET_AGE', 18)。和vue中的methods类似。
actions:和mutations类似。不过actions支持异步操作。第一个参数默认是和store具有相同参数属性的对象。外部调用方式:store.dispatch('nameAsyn')。
modules:store的子模块,内容就相当于是store的一个实例。调用方式和前面介绍的相似,只是要加上当前子模块名,如:store.a.getters.xxx()。
vue-cli中使用vuex的方式
一般来讲,我们都会采用vue-cli来进行实际的开发,在vue-cli中,开发和调用方式稍微不同。1
2
3
4
5
6
7
8
9
10
11
12
13├── index.html
├── main.js
├── components
└── store
├── index.js # 我们组装模块并导出 store 的地方
├── state.js # 跟级别的 state
├── getters.js # 跟级别的 getter
├── mutation-types.js # 根级别的mutations名称(官方推荐mutions方法名使用大写)
├── mutations.js # 根级别的 mutation
├── actions.js # 根级别的 action
└── modules
├── m1.js # 模块1
└── m2.js # 模块2
state.js示例:1
2
3
4
5
6const state = {
name: 'weish',
age: 22
};
export default state;
getters.js示例(我们一般使用getters来获取state的状态,而不是直接使用state):1
2
3
4
5
6
7
8
9
10
11export const name = (state) => {
return state.name;
}
export const age = (state) => {
return state.age
}
export const other = (state) => {
return `My name is ${state.name}, I am ${state.age}.`;
}
mutation-type.js示例(我们会将所有mutations的函数名放在这个文件里):1
2export const SET_NAME = 'SET_NAME';
export const SET_AGE = 'SET_AGE';
mutations.js示例:1
2
3
4
5
6
7
8
9
10mport * as types from './mutation-type.js';
export default {
[types.SET_NAME](state, name) {
state.name = name;
},
[types.SET_AGE](state, age) {
state.age = age;
}
};
actions.js示例(异步操作、多个commit时):1
2
3
4
5
6
7
8import * as types from './mutation-type.js';
export default {
nameAsyn({commit}, {age, name}) {
commit(types.SET_NAME, name);
commit(types.SET_AGE, age);
}
};
modules–m1.js示例(如果不是很复杂的应用,一般来讲是不会分模块的):1
2
3
4
5
6export default {
state: {},
getters: {},
mutations: {},
actions: {}
};
index.js示例(组装vuex):1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25import vue from 'vue';
import vuex from 'vuex';
import state from './state.js';
import * as getters from './getters.js';
import mutations from './mutations.js';
import actions from './actions.js';
import m1 from './modules/m1.js';
import m2 from './modules/m2.js';
import createLogger from 'vuex/dist/logger'; // 修改日志
vue.use(vuex);
const debug = process.env.NODE_ENV !== 'production'; // 开发环境中为true,否则为false
export default new vuex.Store({
state,
getters,
mutations,
actions,
modules: {
m1,
m2
},
plugins: debug ? [createLogger()] : [] // 开发环境下显示vuex的状态修改
});
最后将store实例挂载到main.js里面的vue上去就行了1
2
3
4
5
6
7import store from './store/index.js';
new Vue({
el: '#app',
store,
render: h => h(App)
});
在vue组件中使用时,我们通常会使用mapGetters、mapActions、mapMutations,然后就可以按照vue调用methods和computed的方式去调用这些变量或函数,示例如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20import {mapGetters, mapMutations, mapActions} from 'vuex';
/* 只写组件中的script部分 */
export default {
computed: {
...mapGetters([
name,
age
])
},
methods: {
...mapMutations({
setName: 'SET_NAME',
setAge: 'SET_AGE'
}),
...mapActions([
nameAsyn
])
}
};
参考
IE9以下圆角兼容
使用方法
如何在IE9以下版本的浏览器支持圆角的特性1
2
3
4
5
6
7
8
9
10
11.box {
-moz-border-radius: 15px; /* Firefox */
-webkit-border-radius: 15px; /* Safari 和 Chrome */
border-radius: 15px; /* Opera 10.5+, 以及使用了IE-CSS3的IE浏览器 */
-moz-box-shadow: 10px 10px 20px #000; /* Firefox */
-webkit-box-shadow: 10px 10px 20px #000; /* Safari 和 Chrome */
box-shadow: 10px 10px 20px #000; /* Opera 10.5+, 以及使用了IE-CSS3的IE浏览器 */
behavior: url(ie-css3.htc); /* 通知IE浏览器调用脚本作用于'box'类 */
}
ie-css3.htc
引入下面文件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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354--Do not remove this if you are using--
Original Author: Remiz Rahnas
Original Author URL: http://www.htmlremix.com
Published date: 2008/09/24
Changes by Nick Fetchak:
- IE8 standards mode compatibility
- VML elements now positioned behind original box rather than inside of it - should be less prone to breakage
- Added partial support for 'box-shadow' style
- Checks for VML support before doing anything
- Updates VML element size and position via timer and also via window resize event
- lots of other small things
Published date : 2010/03/14
http://fetchak.com/ie-css3
Thanks to TheBrightLines.com (http://www.thebrightlines.com/2009/12/03/using-ies-filter-in-a-cross-browser-way) for enlightening me about the DropShadow filter
<public:attach event="ondocumentready" onevent="ondocumentready('v08vnSVo78t4JfjH')" />
<script type="text/javascript">
timer_length = 200; // Milliseconds
border_opacity = false; // Use opacity on borders of rounded-corner elements? Note: This causes antialiasing issues
// supportsVml() borrowed from http://stackoverflow.com/questions/654112/how-do-you-detect-support-for-vml-or-svg-in-a-browser
function supportsVml() {
if (typeof supportsVml.supported == "undefined") {
var a = document.body.appendChild(document.createElement('div'));
a.innerHTML = '<v:shape id="vml_flag1" adj="1" />';
var b = a.firstChild;
b.style.behavior = "url(#default#VML)";
supportsVml.supported = b ? typeof b.adj == "object": true;
a.parentNode.removeChild(a);
}
return supportsVml.supported
}
// findPos() borrowed from http://www.quirksmode.org/js/findpos.html
function findPos(obj) {
var curleft = curtop = 0;
if (obj.offsetParent) {
do {
curleft += obj.offsetLeft;
curtop += obj.offsetTop;
} while (obj = obj.offsetParent);
}
return({
'x': curleft,
'y': curtop
});
}
function createBoxShadow(element, vml_parent) {
var style = element.currentStyle['iecss3-box-shadow'] || element.currentStyle['-moz-box-shadow'] || element.currentStyle['-webkit-box-shadow'] || element.currentStyle['box-shadow'] || '';
var match = style.match(/^(\d+)px (\d+)px (\d+)px/);
if (!match) { return(false); }
var shadow = document.createElement('v:roundrect');
shadow.userAttrs = {
'x': parseInt(RegExp.$1 || 0),
'y': parseInt(RegExp.$2 || 0),
'radius': parseInt(RegExp.$3 || 0) / 2
};
shadow.position_offset = {
'y': (0 - vml_parent.pos_ieCSS3.y - shadow.userAttrs.radius + shadow.userAttrs.y),
'x': (0 - vml_parent.pos_ieCSS3.x - shadow.userAttrs.radius + shadow.userAttrs.x)
};
shadow.size_offset = {
'width': 0,
'height': 0
};
shadow.arcsize = element.arcSize +'px';
shadow.style.display = 'block';
shadow.style.position = 'absolute';
shadow.style.top = (element.pos_ieCSS3.y + shadow.position_offset.y) +'px';
shadow.style.left = (element.pos_ieCSS3.x + shadow.position_offset.x) +'px';
shadow.style.width = element.offsetWidth +'px';
shadow.style.height = element.offsetHeight +'px';
shadow.style.antialias = true;
shadow.className = 'vml_box_shadow';
shadow.style.zIndex = element.zIndex - 1;
shadow.style.filter = 'progid:DXImageTransform.Microsoft.Blur(pixelRadius='+ shadow.userAttrs.radius +',makeShadow=true,shadowOpacity='+ element.opacity +')';
element.parentNode.appendChild(shadow);
//element.parentNode.insertBefore(shadow, element.element);
// For window resizing
element.vml.push(shadow);
return(true);
}
function createBorderRect(element, vml_parent) {
if (isNaN(element.borderRadius)) { return(false); }
element.style.background = 'transparent';
element.style.borderColor = 'transparent';
var rect = document.createElement('v:roundrect');
rect.position_offset = {
'y': (0.5 * element.strokeWeight) - vml_parent.pos_ieCSS3.y,
'x': (0.5 * element.strokeWeight) - vml_parent.pos_ieCSS3.x
};
rect.size_offset = {
'width': 0 - element.strokeWeight,
'height': 0 - element.strokeWeight
};
rect.arcsize = element.arcSize +'px';
//rect.arcsize = element.arcSize / (element.offsetWidth + rect.size_offset.width);
rect.strokeColor = element.strokeColor;
rect.strokeWeight = element.strokeWeight +'px';
rect.stroked = element.stroked;
rect.className = 'vml_border_radius';
rect.style.display = 'block';
rect.style.position = 'absolute';
rect.style.top = (element.pos_ieCSS3.y + rect.position_offset.y) +'px';
rect.style.left = (element.pos_ieCSS3.x + rect.position_offset.x) +'px';
rect.style.width = (element.offsetWidth + rect.size_offset.width) +'px';
rect.style.height = (element.offsetHeight + rect.size_offset.height) +'px';
rect.style.antialias = true;
rect.style.zIndex = element.zIndex - 1;
if (border_opacity && (element.opacity < 1)) {
rect.style.filter = 'progid:DXImageTransform.Microsoft.Alpha(Opacity='+ parseFloat(element.opacity * 100) +')';
}
var fill = document.createElement('v:fill');
fill.color = element.fillColor;
fill.src = element.fillSrc;
fill.className = 'vml_border_radius_fill';
fill.type = 'tile';
fill.opacity = element.opacity;
// Hack: IE6 doesn't support transparent borders, use padding to offset original element
isIE6 = /msie|MSIE 6/.test(navigator.userAgent);
if (isIE6 && (element.strokeWeight > 0)) {
element.style.borderStyle = 'none';
element.style.paddingTop = parseInt(element.currentStyle.paddingTop || 0) + element.strokeWeight;
element.style.paddingBottom = parseInt(element.currentStyle.paddingBottom || 0) + element.strokeWeight;
}
rect.appendChild(fill);
element.parentNode.appendChild(rect);
//element.parentNode.insertBefore(rect, element.element);
// For window resizing
element.vml.push(rect);
return(true);
}
function createTextShadow(element, vml_parent) {
if (!element.textShadow) { return(false); }
var match = element.textShadow.match(/^(\d+)px (\d+)px (\d+)px (#?\w+)/);
if (!match) { return(false); }
//var shadow = document.createElement('span');
var shadow = element.cloneNode(true);
var radius = parseInt(RegExp.$3 || 0);
shadow.userAttrs = {
'x': parseInt(RegExp.$1 || 0) - (radius),
'y': parseInt(RegExp.$2 || 0) - (radius),
'radius': radius / 2,
'color': (RegExp.$4 || '#000')
};
shadow.position_offset = {
'y': (0 - vml_parent.pos_ieCSS3.y + shadow.userAttrs.y),
'x': (0 - vml_parent.pos_ieCSS3.x + shadow.userAttrs.x)
};
shadow.size_offset = {
'width': 0,
'height': 0
};
shadow.style.color = shadow.userAttrs.color;
shadow.style.position = 'absolute';
shadow.style.top = (element.pos_ieCSS3.y + shadow.position_offset.y) +'px';
shadow.style.left = (element.pos_ieCSS3.x + shadow.position_offset.x) +'px';
shadow.style.antialias = true;
shadow.style.behavior = null;
shadow.className = 'ieCSS3_text_shadow';
shadow.innerHTML = element.innerHTML;
// For some reason it only looks right with opacity at 75%
shadow.style.filter = '\
progid:DXImageTransform.Microsoft.Alpha(Opacity=75)\
progid:DXImageTransform.Microsoft.Blur(pixelRadius='+ shadow.userAttrs.radius +',makeShadow=false,shadowOpacity=100)\
';
var clone = element.cloneNode(true);
clone.position_offset = {
'y': (0 - vml_parent.pos_ieCSS3.y),
'x': (0 - vml_parent.pos_ieCSS3.x)
};
clone.size_offset = {
'width': 0,
'height': 0
};
clone.style.behavior = null;
clone.style.position = 'absolute';
clone.style.top = (element.pos_ieCSS3.y + clone.position_offset.y) +'px';
clone.style.left = (element.pos_ieCSS3.x + clone.position_offset.x) +'px';
clone.className = 'ieCSS3_text_shadow';
element.parentNode.appendChild(shadow);
element.parentNode.appendChild(clone);
element.style.visibility = 'hidden';
// For window resizing
element.vml.push(clone);
element.vml.push(shadow);
return(true);
}
function ondocumentready(classID) {
if (!supportsVml()) { return(false); }
if (this.className.match(classID)) { return(false); }
this.className = this.className.concat(' ', classID);
// Add a namespace for VML (IE8 requires it)
if (!document.namespaces.v) { document.namespaces.add("v", "urn:schemas-microsoft-com:vml"); }
// Check to see if we've run once before on this page
if (typeof(window.ieCSS3) == 'undefined') {
// Create global ieCSS3 object
window.ieCSS3 = {
'vmlified_elements': new Array(),
'update_timer': setInterval(updatePositionAndSize, timer_length)
};
if (typeof(window.onresize) == 'function') { window.ieCSS3.previous_onresize = window.onresize; }
// Attach window resize event
window.onresize = updatePositionAndSize;
}
// These attrs are for the script and have no meaning to the browser:
this.borderRadius = parseInt(this.currentStyle['iecss3-border-radius'] ||
this.currentStyle['-moz-border-radius'] ||
this.currentStyle['-webkit-border-radius'] ||
this.currentStyle['border-radius'] ||
this.currentStyle['-khtml-border-radius']);
this.arcSize = Math.min(this.borderRadius / Math.min(this.offsetWidth, this.offsetHeight), 1);
this.fillColor = this.currentStyle.backgroundColor;
this.fillSrc = this.currentStyle.backgroundImage.replace(/^url\("(.+)"\)$/, '$1');
//add by emptyhua@gmail.com
if (this.fillSrc == 'none')
this.fillSrc = 'javascript:void(0);';
this.strokeColor = this.currentStyle.borderColor;
this.strokeWeight = parseInt(this.currentStyle.borderWidth);
this.stroked = 'true';
if (isNaN(this.strokeWeight) || (this.strokeWeight == 0)) {
this.strokeWeight = 0;
this.strokeColor = fillColor;
this.stroked = 'false';
}
this.opacity = parseFloat(this.currentStyle.opacity || 1);
this.textShadow = this.currentStyle['text-shadow'];
this.element.vml = new Array();
this.zIndex = parseInt(this.currentStyle.zIndex);
if (isNaN(this.zIndex)) { this.zIndex = 0; }
// Find which element provides position:relative for the target element (default to BODY)
vml_parent = this;
var limit = 100, i = 0;
do {
vml_parent = vml_parent.parentElement;
i++;
if (i >= limit) { return(false); }
} while ((typeof(vml_parent) != 'undefined') && (vml_parent.currentStyle.position != 'relative') && (vml_parent.tagName != 'BODY'));
vml_parent.pos_ieCSS3 = findPos(vml_parent);
this.pos_ieCSS3 = findPos(this);
var rv1 = createBoxShadow(this, vml_parent);
var rv2 = createBorderRect(this, vml_parent);
var rv3 = createTextShadow(this, vml_parent);
if (rv1 || rv2 || rv3) { window.ieCSS3.vmlified_elements.push(this.element); }
if (typeof(vml_parent.document.ieCSS3_stylesheet) == 'undefined') {
vml_parent.document.ieCSS3_stylesheet = vml_parent.document.createStyleSheet();
vml_parent.document.ieCSS3_stylesheet.addRule("v\\:roundrect", "behavior: url(#default#VML)");
vml_parent.document.ieCSS3_stylesheet.addRule("v\\:fill", "behavior: url(#default#VML)");
// Compatibility with IE7.js
vml_parent.document.ieCSS3_stylesheet.ie7 = true;
}
}
function updatePositionAndSize() {
if (typeof(window.ieCSS3.vmlified_elements) != 'object') { return(false); }
for (var i in window.ieCSS3.vmlified_elements) {
var el = window.ieCSS3.vmlified_elements[i];
if (typeof(el.vml) != 'object') { continue; }
for (var z in el.vml) {
//var parent_pos = findPos(el.vml[z].parentNode);
var new_pos = findPos(el);
new_pos.x = (new_pos.x + el.vml[z].position_offset.x) + 'px';
new_pos.y = (new_pos.y + el.vml[z].position_offset.y) + 'px';
if (el.vml[z].style.left != new_pos.x) { el.vml[z].style.left = new_pos.x; }
if (el.vml[z].style.top != new_pos.y) { el.vml[z].style.top = new_pos.y; }
var new_size = {
'width': parseInt(el.offsetWidth + el.vml[z].size_offset.width),
'height': parseInt(el.offsetHeight + el.vml[z].size_offset.height)
}
if (el.vml[z].offsetWidth != new_size.width) { el.vml[z].style.width = new_size.width +'px'; }
if (el.vml[z].offsetHeight != new_size.height) { el.vml[z].style.height = new_size.height +'px'; }
}
}
if (event && (event.type == 'resize') && typeof(window.ieCSS3.previous_onresize) == 'function') { window.ieCSS3.previous_onresize(); }
}
// added by emptyhua@gmail.com 2010 7-3
window.update_css3_fix_position = updatePositionAndSize;
window.update_css3_fix = function(el)
{
if (!el.vml) return;
el.arcSize = Math.min(el.borderRadius / Math.min(el.offsetWidth, el.offsetHeight), 1);
// Find which element provides position:relative for the target element (default to BODY)
var vml_parent = el;
var limit = 100, i = 0;
do {
vml_parent = vml_parent.parentElement;
i++;
if (i >= limit) { return(false); }
} while ((typeof(vml_parent) != 'undefined') && (vml_parent.currentStyle.position != 'relative') && (vml_parent.tagName != 'BODY'));
vml_parent.pos_ieCSS3 = findPos(vml_parent);
el.pos_ieCSS3 = findPos(el);
for (i in el.vml)
{
element.parentNode.removeChild(el.vml[i]);
}
el.vml = [];
var rv1 = createBoxShadow(el, vml_parent);
var rv2 = createBorderRect(el, vml_parent);
};
</script>
asyuc函数
含义
async 函数是什么?一句话,它就是 Generator 函数的语法糖。
前文有一个 Generator 函数,依次读取两个文件。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17const fs = require('fs');
const readFile = function (fileName) {
return new Promise(function (resolve, reject) {
fs.readFile(fileName, function(error, data) {
if (error) return reject(error);
resolve(data);
});
});
};
const gen = function* () {
const f1 = yield readFile('/etc/fstab');
const f2 = yield readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
写成async函数1
2
3
4
5
6const asyncReadFile = async function () {
const f1 = await readFile('/etc/fstab');
const f2 = await readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
一比较就会发现,async 函数就是将 Generator 函数的星号(*)替换成 async,将 yield 替换成 await,仅此而已
优点
内置执行器。
Generator 函数的执行必须靠执行器,所以才有了co模块,而async函数自带执行器。也就是说,async函数的执行,与普通函数一模一样,只要一行。
更好的语义
async和await,比起星号和yield,语义更清楚了。async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。
更广的适用性
co模块约定,yield命令后面只能是 Thunk 函数或 Promise 对象,而async函数的await命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)。
返回值是 Promise
async函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了。你可以用then方法指定下一步的操作。
基本用法
async函数返回一个 Promise 对象,可以使用then方法添加回调函数。当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。
1 | async function getStockPriceByName(name) { |
async 函数有多种使用形式。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// 函数声明
async function foo() {}
// 函数表达式
const foo = async function () {};
// 对象的方法
let obj = { async foo() {} };
obj.foo().then(...)
// Class 的方法
class Storage {
constructor() {
this.cachePromise = caches.open('avatars');
}
async getAvatar(name) {
const cache = await this.cachePromise;
return cache.match(`/avatars/${name}.jpg`);
}
}
const storage = new Storage();
storage.getAvatar('jake').then(…);
// 箭头函数
const foo = async () => {};
语法
async函数的语法规则总体上比较简单,难点是错误处理机制。
Promise对象的状态变化
async函数返回的 Promise 对象,必须等到内部所有await命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到return语句或者抛出错误。也就是说,只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数。1
2
3
4
5
6
7async function getTitle(url) {
let response = await fetch(url);
let html = await response.text();
return html.match(/<title>([\s\S]+)<\/title>/i)[1];
}
getTitle('https://tc39.github.io/ecma262/').then(console.log)
// "ECMAScript 2017 Language Specification"
上面代码中,函数getTitle内部有三个操作:抓取网页、取出文本、匹配页面标题。只有这三个操作全部完成,才会执行then方法里面的console.log。
await
await命令后面是一个 Promise 对象。如果不是,会被转成一个立即resolve的 Promise 对象1
2
3
4
5
6async function f() {
return await 123;
}
f().then(v => console.log(v))
// 123
参考
class继承
ES6提供了更接近传统语言的写法,引入了Class(类)这个概念。新的class写法让对象原型的写法更加清晰、更像面向对象编程的语法,也更加通俗易懂。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
38class Animal {
// 构造方法,实例化的时候将会被调用,如果不指定,那么会有一个不带参数的默认构造函数.
constructor(name,color) {
this.name = name;
this.color = color;
}
// toString 是原型对象上的属性
toString() {
console.log('name:' + this.name + ',color:' + this.color);
}
}
var animal = new Animal('dog','white');
animal.toString();
console.log(animal.hasOwnProperty('name')); //true
console.log(animal.hasOwnProperty('toString')); // false
console.log(animal.__proto__.hasOwnProperty('toString')); // true
class Cat extends Animal {
constructor(action) {
// 子类必须要在constructor中指定super 方法,否则在新建实例的时候会报错.
// 如果没有置顶consructor,默认带super方法的constructor将会被添加、
super('cat','white');
this.action = action;
}
toString() {
console.log(super.toString());
}
}
var cat = new Cat('catch')
cat.toString();
// 实例cat 是 Cat 和 Animal 的实例,和Es5完全一致。
console.log(cat instanceof Cat); // true
console.log(cat instanceof Animal); // true
上面代码首先用class定义了一个“类”,可以看到里面有一个constructor方法,这就是构造方法,而this关键字则代表实例对象。简单地说,constructor内定义的方法和属性是实例对象自己的,而constructor外定义的方法和属性则是所有实例对象可以共享的。
Class之间可以通过extends关键字实现继承,这比ES5的通过修改原型链实现继承,要清晰和方便很多。上面定义了一个Cat类,该类通过extends关键字,继承了Animal类的所有属性和方法。
super关键字,它指代父类的实例(即父类的this对象)。子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工。如果不调用super方法,子类就得不到this对象。
ES6的继承机制,实质是先创造父类的实例对象this(所以必须先调用super方法),然后再用子类的构造函数修改this。
ES6 学习笔记总结
前言
ECMAScript 6(以下简称ES6)是JavaScript语言的下一代标准。因为当前版本的ES6是在2015年发布的,所以又称ECMAScript 2015。
Babel转码器
Babel
Babel是一个广泛使用的ES6转码器,可以将ES6代码转为ES5代码,从而在现有环境执行。大家可以选择自己习惯的工具来使用使用Babel,具体过程可直接在Babel官网查看:
let和const命令
基本用法
用来声明变量,但是所有声明的变量只在let命令所在的代码块中有效;1
2
3
4
5
6
7var a = [];
for (let i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 6
变量i是let声明的,当前的i只在本轮循环有效,所以每一次循环的i其实都是一个新的变量,所以最后输出的是6。
不存在变量提升
let命令改变了语法行为,它所声明的变量一定要在声明后使用,否则报错。1
2
3// let 的情况
console.log(error); // 报错ReferenceError
let error = 2;
块级作用域
1 | function fun() { |
上面的函数有两个代码块,都声明了变量n,运行后输出 5。这表示外层代码块不受内层代码块的影响。
不能重复声明变量
let不允许在相同作用域内,重复声明同一个变量。1
2
3
4
5
6
7
8
9
10
11
12
13function func() {
let a = 10;
let a = 1;
console.log(a); // 报错
}
function func(a) {
let a; // 报错
}
function func(a) {
{
let a; // 不报错
}
}
const
const也用来声明变量,但是声明的是常量。一旦声明,常量的值就不能改变。
const命令的常量 也是不提升,同样存在暂时性死区,只在声明所在的块级作用域内有效.
const声明的常量,也与let一样不可重复声明。1
2
3
4
5var message = "Hello!";
let age = 25;
const message = "Goodbye!";// 报错
const age = 30;// 报错
和let一样不可重复声明
1 | if (true) { |
块级作用域
变量的解构赋值
ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构1
2
3
4
5
6
7
8
9
10let { foo, bar } = { foo: "aaa", bar: "bbb" };
foo // "aaa"
bar // "bbb"
----------------------
const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"
Symbol
ES6 引入了一种新的原始数据类型Symbol,表示独一无二的值。
它是 JavaScript 语言的第七种数据类型,前六种是:undefined、null、布尔值(Boolean)、字符串(String)、数值(Number)、对象(Object)。
注意,Symbol函数前不能使用new命令,否则会报错。这是因为生成的Symbol是一个原始类型的值,不是对象
Symbol函数可以接受一个字符串作为参数,表示对Symbol实例的描述,主要是为了在控制台显示,或者转为字符串时,比较容易区分。1
2
3
4
5
6
7
8// 没有参数的情况
var s1 = Symbol();
var s2 = Symbol();
s1 === s2 // false
// 有参数的情况
var s1 = Symbol("foo");
var s2 = Symbol("foo");
s1 === s2 // false
s1和s2都是Symbol函数的返回值,而且参数相同,但是它们是不相等的。
1 | let sym = Symbol('My symbol'); |
Symbol 值不能与其他类型的值进行运算,会报错
箭头函数
ES6 允许使用“箭头”(=>)定义函数,支持expression 和 statement 两种形式。同时一点很重要的是它拥有词法作用域的this值,帮你很好的解决this的指向问题,这是一个很酷的方式,可以帮你减少一些代码的编写。
注意
(1)函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
(2)不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
(3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
(4)不可以使用yield命令,因此箭头函数不能用作 Generator 函数。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17function Timer() {
this.s1 = 0;
this.s2 = 0;
// 箭头函数
setInterval(() => this.s1++, 1000);
// 普通函数
setInterval(function () {
this.s2++;
}, 1000);
}
var timer = new Timer();
setTimeout(() => console.log('s1: ', timer.s1), 3100);
setTimeout(() => console.log('s2: ', timer.s2), 3100);
// s1: 3
// s2: 0
Timer函数内部设置了两个定时器,分别使用了箭头函数和普通函数。前者的this绑定定义时所在的作用域(即Timer函数),后者的this指向运行时所在的作用域(即全局对象)。所以,3100 毫秒之后,timer.s1被更新了 3 次,而timer.s2一次都没更新。
1 | class Animal { |
当我们使用箭头函数时,函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
并不是因为箭头函数内部有绑定this的机制,实际原因是箭头函数根本没有自己的this,它的this是继承外面的,因此内部的this就是外层代码块的this。
Module语法
在 ES6 之前,社区制定了一些模块加载方案,最主要的有 CommonJS 和 AMD 两种。前者用于服务器,后者用于浏览器。ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案。
ES6 模块的设计思想,是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS 和 AMD 模块,都只能在运行时确定这些东西。比如,CommonJS 模块就是对象,输入时必须查找对象属性。
export
ES6将一个文件视为一个模块,上面的模块通过 export 向外输出了一个变量。一个模块也可以同时往外面输出多个变量。
1 | // profile.js |
import命令
定义好模块的输出以后就可以在另外一个模块通过import引用。1
2 //index.js
import {name, age} from './profile.js'
default
不用关系模块输出了什么,通过 export default 指令就能加载到默认模块,不需要通过 花括号来指定输出的模块,一个模块只能使用 export default 一次1
2
3
4// default 导出
export default class { ... }
// 导入的时候不需要花括号
import test from './test.js';
参考
搭建hexo中遇到的坑
hexo 下的分类和表签无法显示,解决方法
打开页面的时候标签和分类总是提示Cannot GET /tags/这个错误
新建一个页面
$ hexo new page "tags"
设置新建页面的类型
(\source\tags\index.md中查找设置)1
2
3
4
5
6---
title: tags
date: 2017-11-12 22:06:40
tags: tags #文章的标签
---
注意:冒号后面都需要添加一个空格
配置主题文件
(主题_config.yml中设置)1
2
3menu:
home: / || home
tags: /tags/ || tags #确保已经打开
重新生成生成
1 | hexo generate |
1 | 如果上述都设置都没有问题还是不能生成页面 |
github上建立仓库注意点
设置仓库名字的时候 ghshuo.github.io ,其中ghshuo 必须要是你的用户名,其它名称无效
将来你的网站访问地址就是 http://ghshuo.github.io
绑定自己的域名
1 | 记录类型选A或CNAME,A记录的记录值就是ip地址, |
hexo文章中添加图片
每次要把图片先上传到七牛的服务器然后再使用有点麻烦,下面的方法在本地加载
修改主配置
主页配置中 _config.yml 中有 post_asset_folder: true 如果是flase 改成true
hexo目录中执行
npm install https://github.com/CodeFalling/hexo-asset-image –save
生成新的文章
1 | $ hexo new [hexo-pit] <title> 建一篇新文章hexo-pit |