Vue

指南:如何使用vue.js jQuery插件

使用Vue可真棒。但你知道,它也可以让你头痛,当你试图将它与jQuery插件或其他图书馆。

问题是,jQuery的作品比Vue根本不同。Vue渲染基于组件的核心数据的一切。jQuery是大多是做简单的点击操作,具有强大的能力操纵DOM。
当我寻找帮助我发现一些不令人满意的解决方案组件做所有的初始化工作。
让我告诉你我是如何将vue.js jQuery。

目标是什么?

在大多数情况下,你可以省去使用jQuery和找到一个简单的基于Vue对你的问题的解决方案。情态动词、滑块等相当简单与Vue组件和CSS。

所以我们的目标是使用强大的jQuery插件,不能写很快在Vue。

我们将…

  • …使用VUE指令建立一个桥梁,jQuery。

  • ……当元素被连接时初始化这个插件。

  • 当元素被分离时,破坏它。

  • ……发送事件通知组件。

  • ……从组件中接收事件并将它们传递给插件。

教程的时间

我拿起冯元辰的农作物的插件,因为它是一个写得很好的jQuery插件,你可能不能改写接下来的60分钟。vue.js单独使用。
它的复杂和演示了如何vue.js可以与它进行交互。
演示:https://vue-jquery-cropper-demo.firebaseapp.com/
我要描述如何从一开始就开始工作。跳过你已经做的部分。

创建项目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# install vue-cli
$ npm install -g vue-cli
# create a new project using the "webpack" boilerplate
$ vue init webpack vue-cropper
? Project name "vue-cropper"
? Project description "A Vue.js project"
? Author "Christian Gambardella <christian@gambardella.info>"
? Use ESLint to lint your code? "Yes"
? Pick an ESLint preset "Standard"
? Setup unit tests with Karma + Mocha? "No"
? Setup e2e tests with Nightwatch? "No"
$ cd vue-cropper
$ npm install

恭喜你有一个vue.js项目。

安装jQuery和cropper.js

1
2
# install jQuery & cropper
$ npm install jquery cropper --save

配置WebPACK jQuery和Vue指令

添加jQuery源和指令到WebPACK别名地图文件夹。
通常会包括WebPACK的编译版本的jQuery。建议是有源码包括而不是。
你可以看到,Vue WebPACK模板添加组件的文件夹。我通常添加其他文件夹一样的指令,混入,等这个例子中我们只需要添加指令。

这将有助于我们导入依赖关系,而不必知道确切的路径。这也有利于当你重构你的应用程序的路上。你不需要管理相对路径。
编辑生成/ webpack.base.conf.js添加高亮线。

1
2
3
4
5
6
7
8
9
10
11
resolve: {
extensions: ['', '.js', '.vue'],
fallback: [path.join(__dirname, '../node_modules')],
alias: {
'src': path.resolve(__dirname, '../src'),
'assets': path.resolve(__dirname, '../src/assets'),
'components': path.resolve(__dirname, '../src/components'),
'jquery': path.resolve(__dirname, '../node_modules/jquery/src/jquery'),
'directives': path.resolve(__dirname, '../src/directives')
}
},

别忘了在组件线的末端加一个逗号。

准备应用程序组件

我将开始与组件,因为我相信这是更容易理解的美丽和简单的组件。你可以立即开始写指令,因为你知道如何在组件中使用它们。
SRC / app.vue更换模板

1
2
3
4
5
6
7
8
<template>
<div id="app">
<img
v-cropper="cropOptions"
src="https://i.imgur.com/WcvkCxl.png"
alt="jQuery Meme">
</div>
</template>

SRC / app.vue更换模板

<script>
import Cropper from './directives/Cropper'

export default {
  directives: {
    Cropper
  },

  data () {
    return {
      cropOptions: {
        viewMode: 0,
        zoomable: false
      }
    }
  }
}
</script>

这里有几个重要的事情要注意。

  • 我们不进口jQuery也没有处理组件的初始化。
  • 农作物的可供选择的原始数据。
  • 农作物将被初始化时,这个被渲染,将当视图不可见了破坏。
  • 该指令的名字将在模板中可作为一个串精装版。mycropper会成为我的割草机。初始化,我们已经添加v-my-cropper在构件的模板的一个元素。

    创建农作物指令

    这是本教程的核心。我们要创建一个农作物的指令,与较低的DOM操作的交易代码。在这种情况下我们要初始化的农作物。
    自定义指令提供测绘数据的变化任意DOM的行为机制。
    http://vuejs.org/guide/custom-directive.html
创建src /指令/ Cropper.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
JavaScript
import jQuery from 'jquery'
import 'cropper'
export default {
deep: true,
bind () {},
update (options) {
// Destroy in case it has been initialized already.
jQuery(this.el).cropper('destroy')
// Initializing directly after destroying
// didn't work. Wrapping it in a setTimeout
// seems to do the trick.
setTimeout(() => {
jQuery(this.el).cropper(options)
}, 0)
},
unbind () {
jQuery(this.el).cropper('destroy')
}
}
import jQuery from 'jquery'
import 'cropper'
export default {
deep: true,
bind () {},
update (options) {
// Destroy in case it has been initialized already.
jQuery(this.el).cropper('destroy')
// Initializing directly after destroying
// didn't work. Wrapping it in a setTimeout
// seems to do the trick.
setTimeout(() => {
jQuery(this.el).cropper(options)
}, 0)
},
unbind () {
jQuery(this.el).cropper('destroy')
}
}

每一vue.js指令的核心是一个绑定更新绑定功能简单的对象。

  • 绑定:一旦元素被连接到文档上,将被调用。
  • 更新:绑定后调用一次,每次当有线数据改变时。在这种情况下cropoptions是一个对象。的vue.js指令需要知道它是一个对象。这就是为什么我们需要加深:在这种情况下,真的。
  • 取消绑定:当DOM元素将被删除。我们在这里摧毁一切,并删除所有的事件侦听器。
    会有一对夫妇在Vue 2的变化。

的虚拟机实例将作为函数参数传递,不会被设置为。

指令的更新功能只会被调用在更新,而不是最初绑定后。

第一次运行

我们只是做了最低限度的。让我们看看它是否工作到目前为止。

只有一个快速的东西失踪。我们没有增加农作物的样式呢。让我们这样做,并运行应用程序。

添加第一,强调线的SRC / main.js

1
2
3
4
5
6
7
8
9
import '../node_modules/cropper/dist/cropper.css'
import Vue from 'vue'
import App from './App'
/* eslint-disable no-new */
new Vue({
el: 'body',
components: { App }
})
启动服务器。
$ npm run dev

拨动开关

从这里开始一切都会很容易。我们将通过它的情况下。但每一个都会几乎太简单。
让我们来启用和禁用农作物与按钮。想从网页或打开农作物在用户选择了一个文件。这表明,农作物可以在任何时间进行初始化。
编辑在SRC / app.vue 脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import Cropper from './directives/Cropper'
export default {
directives: {
Cropper
},
data () {
return {
cropOptions: {
viewMode: 0,
zoomable: false
},
showCropper: false
}
},
methods: {
toggleCropper () {
this.showCropper = !this.showCropper
}
}
}
  • 添加showcropper:虚假数据。
  • 添加一个方法来切换showcropper。

编辑在SRC / app.vue 模板

1
2
3
4
5
6
7
8
<div id="app">
<button id="toggle" @click="toggleCropper">Toggle Cropper</button>
<img
v-if="showCropper"
v-cropper="cropOptions"
src="https://i.imgur.com/WcvkCxl.png"
alt="Mustache">
</div>
  • 添加一个“单击处理程序”按钮。

  • 添加一个v-if指令img标签。

每一次的图像显示了自定义指令的结合收割机和更新功能将被称为。

当showcropper设置为false农作物的绑定函数将被调用。

没有太多我们可以从这里搞砸了。

农作物将装在需要卸载后用户完成。

改变选项

我们已经都有更新的农作物的选项。如果我们有一个插件,会接受新的选择现有的jQuery实例,那么我们就不会产生破坏和重建的插件。不幸的是,也不允许。

让我们添加一个开关,改变图像是否是可缩放。
编辑在SRC / app.vue 模板

1
2
3
4
5
6
7
8
9
10
<div id="app">
<button id="toggle" @click="toggleCropper">Toggle Cropper</button>
<img
v-if="showCropper"
v-cropper="cropOptions"
src="https://i.imgur.com/WcvkCxl.png"
alt="Mustache">
<input id="zoomable" type="checkbox" v-model="cropOptions.zoomable">
<label for="zoomable">Zoomable?</label>
</div>

容易,对吗?

从指令中获取数据

农作物有农作物的回调函数。每当回调将被调用时,我们将发出一个事件。
编辑SRC /指令/ Cropper.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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
import 'cropper'
export default {
deep: true,
bind () {},
update (options) {
options.crop = (event) => {
this.vm.$emit('crop', event)
}
// Destroy in case it has been initialized already.
jQuery(this.el).cropper('destroy')
// Initializing directly after destroying
// didn't work. Wrapping it in a setTimeout
// seems to do the trick.
setTimeout(() => {
jQuery(this.el).cropper(options)
}, 0)
},
unbind () {
jQuery(this.el).cropper('destroy')
}
}
Edit the App component’s script to handle crop events.
import Cropper from './directives/Cropper'
export default {
directives: {
Cropper
},
data () {
return {
cropData: {},
cropOptions: {
viewMode: 0,
zoomable: false
},
showCropper: false
}
},
events: {
crop (event) {
this.cropData = {
x: event.x,
y: event.y,
width: event.width,
height: event.height
}
}
},
methods: {
toggleCropper () {
this.showCropper = !this.showCropper
}
}
}
import Cropper from './directives/Cropper'
export default {
directives: {
Cropper
},
data () {
return {
cropData: {},
cropOptions: {
viewMode: 0,
zoomable: false
},
showCropper: false
}
},
events: {
crop (event) {
this.cropData = {
x: event.x,
y: event.y,
width: event.width,
height: event.height
}
}
},
methods: {
toggleCropper () {
this.showCropper = !this.showCropper
}
}
}

编辑 应用组件的模板来显示作物数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>
<div id="app">
<button id="toggle" @click="toggleCropper">Toggle Cropper</button>
<img
v-if="showCropper"
v-cropper="cropOptions"
src="https://i.imgur.com/WcvkCxl.png"
alt="Mustache">
<input id="zoomable" type="checkbox" v-model="cropOptions.zoomable">
<label for="zoomable">Zoomable?</label>
<pre id="output">
x: {{ cropData.x }}
y: {{ cropData.y }}
width: {{ cropData.width }}
height: {{ cropData.height }}
</pre>
</div>
</template>

正如你所看到的,我选择了从指令中发射事件,并在应用程序组件中听他们的。问你自己为什么我选择这样做。我也可以选择创建一个方法在应用程序组件和使用该绑定到农作物的作物的倾听者。

反模式options.crop = this.vm.handlecrop

唤起行动从外面

这个最终的更改显示事件如何从一个组件流到一个指令。我们要用那把农作物的行为。让我给你展示我如何添加一个旋转按钮。

编辑 应用程序组件的模板,并添加旋转按钮。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<template>
<div id="app">
<button id="toggle" @click="toggleCropper">Toggle Cropper</button>
<img
v-if="showCropper"
v-cropper="cropOptions"
src="https://i.imgur.com/WcvkCxl.png"
alt="Mustache">
<input id="zoomable" type="checkbox" v-model="cropOptions.zoomable">
<label for="zoomable">Zoomable?</label>
<button @click="$emit('rotate', 90)">Rotate 90°</button>
<pre id="output">
x: {{ cropData.x }}
y: {{ cropData.y }}
width: {{ cropData.width }}
height: {{ cropData.height }}
</pre>
</div>
</template>

编辑SRC /指令/ cropper.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
import jQuery from 'jquery'
import 'cropper'
export default {
deep: true,
bind () {
this.vm.$on('rotate', (deg) => {
jQuery(this.el).cropper('rotate', deg)
})
},
update (options) {
options.crop = (event) => {
this.vm.$emit('crop', event)
}
// Destroy in case it has been initialized already.
jQuery(this.el).cropper('destroy')
// Initializing directly after destroying
// didn't work. Wrapping it in a setTimeout
// seems to do the trick.
setTimeout(() => {
jQuery(this.el).cropper(options)
}, 0)
},
unbind () {
jQuery(this.el).cropper('destroy')
this.vm.$off('rotate')
}
}

总结

等你做à圈!

这个设置的美丽是,该指令是从组件解耦,可以使用每一个组件的多次。它将自动初始化后,元素被附加到DOM将之前被删除销毁。

我希望你喜欢这篇文章。如果你有问题的建议,随时给我留言或发邮件给评论部分。

最好的,

转载自(并翻译于):http://gambardella.info/2016/09/05/guide-how-to-use-vue-js-with-jquery-plugins

javascriptStorage

Javascript 本地存储小结

前言

总括:详细讲述Cookie,LocalStorge,SesstionStorge的区别和用法。
人生如画,岁月如歌。

原文博客地址:Javascript本地存储小结

知乎专栏&&简书专题:前端进击者(知乎)&&前端进击者(简书)

1. 各种存储方案的简单对比

  • Cookies:浏览器均支持,容量为4KB
  • UserData:仅IE支持,容量为64KB
  • Flash:100KB,非HTML原生,需要插件支持
  • Google Gears SQLite :需要插件支持,容量无限制
  • LocalStorage:HTML5,容量为5M
  • SesstionStorage:HTML5,容量为5M
  • globalStorage:Firefox独有的,Firefox13开始就不再支持这个方法

    UserData仅IE支持, Google Gears SQLite需要插件,Flash已经伴随着HTML5的出现渐渐退出了历史舞台,因此今天我们的主角只有他们三个:Cookie,LocalStorge,SesstionStorge;
    

作为一个前端和Cookie打交道的次数肯定不会少了,Cookie算是比较古老的技术了
1993 年,网景公司雇员 Lou Montulli 为了让用户在访问某网站时,进一步提高访问速度,同时也为了进一步实现个人化网络,发明了今天广泛使用的 Cookie。

2.1 Cookie的特点

我们先来看下Cookie的特点:
  • 1)cookie的大小受限制,cookie大小被限制在4KB,不能接受像大文件或邮件那样的大数据。

  • 2)只要有请求涉及cookie,cookie就要在服务器和浏览器之间来回传送(这解释为什么本地文件不能测试cookie)。而且cookie数据始终在同源的http请求中携带(即使不需要),这也是Cookie不能太大的重要原因。正统的cookie分发是通过扩展HTTP协议来实现的,服务器通过在HTTP的响应头中加上一行特殊的指示以提示浏览器按照指示生成相应的cookie。

  • 3)用户每请求一次服务器数据,cookie则会随着这些请求发送到服务器,服务器脚本语言如PHP等能够处理cookie发送的数据,可以说是非常方便的。当然前端也是可以生成Cookie的,用js对cookie的操作相当的繁琐,浏览器只提供document.cookie这样一个对象,对cookie的赋值,获取都比较麻烦。而在PHP中,我们可以通过setcookie()来设置cookie,通过$_COOKIE这个超全局数组来获取cookie。

    cookie的内容主要包括:名字,值,过期时间,路径和域。路径与域一起构成cookie的作用范围。若不设置过期时间,则表示这个cookie的生命期为浏览器会话期间,关闭浏览器窗口,cookie就消失。这种生命期为浏览器会话期的cookie被称为会话cookie。会话cookie一般不存储在硬盘上而是保存在内存里,当然这种行为并不是规范规定的。若设置了过期时间,浏览器就会把cookie保存到硬盘上,关闭后再次打开浏览器,这些cookie仍然有效直到超过设定的过期时间。存储在硬盘上的cookie可以在不同的浏览器进程间共享,比如两个IE窗口。而对于保存在内存里的cookie,不同的浏览器有不同的处理方式。
    

2.2 Session

说到Cookie就不能不说Session。

Session机制。session机制是一种服务器端的机制,服务器使用一种类似于散列表的结构(也可能就是使用散列表)来保存信息。当程序需要为某个客户端的请求创建一个session时,服务器首先检查这个客户端的请求里是否已包含了一个session标识(称为session id),如果已包含则说明以前已经为此客户端创建过session,服务器就按照session id把这个session检索出来使用(检索不到,会新建一个),如果客户端请求不包含session id,则为此客户端创建一个session并且生成一个与此session相关联的session id,session id的值应该是一个既不会重复,又不容易被找到规律以仿造的字符串,这个session id将被在本次响应中返回给客户端保存。保存这个session id的方式可以采用cookie,这样在交互过程中浏览器可以自动的按照规则把这个标识发送给服务器。一般这个cookie的名字都是类似于SEEESIONID。但cookie可以被人为的禁止,则必须有其他机制以便在cookie被禁止时仍然能够把session id传递回服务器。经常被使用的一种技术叫做URL重写,就是把session id直接附加在URL路径的后面。比如:http://damonare.cn?sessionid=123456还有一种技术叫做表单隐藏字段。就是服务器会自动修改表单,添加一个隐藏字段,以便在表单提交时能够把session id传递回服务器。比如:

输入框

实际上这种技术可以简单的用对action应用URL重写来代替。

2.3 Cookie和Session简单对比

Cookie和Session 的区别:
  • 1)cookie数据存放在客户的浏览器上,session数据放在服务器上。

  • 2)cookie不是很安全,别人可以分析存放在本地的cookie并进行cookie欺骗,考虑到安全应当使用session。

  • 3)session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能考虑到减轻服务器性能方面,应当使用cookie。

  • 4)单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie。

  • 5)所以建议:
    将登陆信息等重要信息存放为SESSION
    其他信息如果需要保留,可以放在cookie中

    2.4 document.cookie的属性

expires属性
1
指定了coolie的生存期,默认情况下coolie是暂时存在的,他们存储的值只在浏览器会话期间存在,当用户推出浏览器后这些值也会丢失,如果想让cookie存在一段时间,就要为expires属性设置为未来的一个过期日期。现在已经被max-age属性所取代,max-age用秒来设置cookie的生存期。
path属性
1
它指定与cookie关联在一起的网页。在默认的情况下cookie会与创建它的网页,该网页处于同一目录下的网页以及与这个网页所在目录下的子目录下的网页关联。
domain属性
1
domain属性可以使多个web服务器共享cookie。domain属性的默认值是创建cookie的网页所在服务器的主机名。不能将一个cookie的域设置成服务器所在的域之外的域。例如让位于order.damonare.cn的服务器能够读取catalog.damonare.cn设置的cookie值。如果catalog.damonare.cn的页面创建的cookie把自己的path属性设置为“/”,把domain属性设置成“.damonare.cn”,那么所有位于catalog.damonare.cn的网页和所有位于orlders.damonare.cn的网页,以及位于damonare.cn域的其他服务器上的网页都可以访问这个cookie。
secure属性
1
它是一个布尔值,指定在网络上如何传输cookie,默认是不安全的,通过一个普通的http连接传输

2.5 cookie实战

这里我们使用javascript来写一段cookie,借用w3cschool的demo:
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 getCookie(c_name){
if (document.cookie.length>0){
c_start=document.cookie.indexOf(c_name + "=")
if (c_start!=-1){
c_start=c_start + c_name.length+1
c_end=document.cookie.indexOf(";",c_start)
if (c_end==-1) c_end=document.cookie.length
return unescape(document.cookie.substring(c_start,c_end))
}
}
return "";
}
function setCookie(c_name,value,expiredays){
var exdate=new Date()
exdate.setDate(exdate.getDate()+expiredays)
document.cookie=c_name+ "=" +escape(value)+
((expiredays==null) ? "" : "; expires="+exdate.toUTCString())
}
function checkCookie(){
username=getCookie('username')
if(username!=null && username!=""){alert('Welcome again '+username+'!')}
else{
username=prompt('Please enter your name:',"")
if (username!=null && username!=""){
setCookie('username',username,355)
}
}
}
注意这里对Cookie的生存期进行了定义,也就是355天

3. localStorage

1
2
这是一种持久化的存储方式,也就是说如果不手动清除,数据就永远不会过期。
它也是采用Key - Value的方式存储数据,底层数据接口是sqlite,按域名将数据分别保存到对应数据库文件里。它能保存更大的数据(IE8上是10MB,Chrome是5MB),同时保存的数据不会再发送给服务器,避免带宽浪费。

3.1 localStorage的属性方法

下表是localStorge的一些属性和方法

属性方法 说明
localStorage.length 获得storage中的个数
localStorage.key(n) 获得storage中第n个元素对的键值(第一个元素是0)
localStorage.getItem(key) 获取键值key对应的值
localStorage.key 获取键值key对应的值
localStorage.setItem(key, value) 添加数据,键值为key,值为value
localStorage.removeItem(key) 移除键值为key的数据
localStorage.clear() 清除所有数据

3.2 localStorage的缺点

  • ① localStorage大小限制在500万字符左右,各个浏览器不一致
  • ② localStorage在隐私模式下不可读取
  • ③ localStorage本质是在读写文件,数据多的话会比较卡(firefox会一次性将数据导入内存,想想就觉得吓人啊)
  • ④ localStorage不能被爬虫爬取,不要用它完全取代URL传参

4. sessionStorage

1
和服务器端使用的session类似,是一种会话级别的缓存,关闭浏览器会数据会被清除。不过有点特别的是它的作用域是窗口级别的,也就是说不同窗口间的sessionStorage数据不能共享的。使用方法(和localStorage完全相同):
属性方法 说明
sessionStorage.length 获得storage中的个数
sessionStorage.key(n) 获得storage中第n个元素对的键值(第一个元素是0)
sessionStorage.getItem(key) 获取键值key对应的值
sessionStorage.key 获取键值key对应的值
sessionStorage.setItem(key, value) 添加数据,键值为key,值为value
sessionStorage.removeItem(key) 移除键值为key的数据
sessionStorage.clear() 清除所有数据

5. sessionStorage和localStorage的区别

  • sessionStorage用于本地存储一个会话(session)中的数据,这些数据只有在同一个会话中的页面才能访问并且当会话结束后数据也随之销毁。因此sessionStorage不是一种持久化的本地存储,仅仅是会话级别的存储。当用户关闭浏览器窗口后,数据立马会被删除。

  • localStorage用于持久化的本地存储,除非主动删除数据,否则数据是永远不会过期的。第二天、第二周或下一年之后,数据依然可用。

5.1 测试

sessionStorage:
1
2
3
4
5
6
if (sessionStorage.pagecount){
sessionStorage.pagecount=Number(sessionStorage.pagecount) +1;
}else{
sessionStorage.pagecount=1;
}
console.log("Visits "+ sessionStorage.pagecount + " time(s).");
测试过程:我们在控制台输入上述代码查看打印结果
控制台首次输入代码:

sessionStorage测试结果

关闭窗口,控制台再次输入代码:

sessionStorage测试结果

所谓的关闭窗口即销毁,就是这样,关闭窗口重新打开输入代码输出结果还是上面图片的样子,也就是说关闭窗口后sessionStorage.pagecount即被销毁,除非重心创建。或者从历史记录进入才会相关数据才会存在。好的,我们再来看下localStorge表现:
1
2
3
4
5
6
if (localStorage.pagecount){
localStorage.pagecount=Number(localStorage.pagecount) +1;
}else{
localStorage.pagecount=1;
}
console.log("Visits "+ localStorage.pagecount + " time(s).");
控制台首次输入代码:

localStorage测试结果1

关闭窗口,控制台再次输入代码:

localStorage测试结果2

6. web Storage和cookie的区别

Web Storage(localStorage和sessionStorage)的概念和cookie相似,区别是它是为了更大容量存储设计的。Cookie的大小是受限的,并且每次你请求一个新的页面的时候Cookie都会被发送过去,这样无形中浪费了带宽,另外cookie还需要指定作用域,不可以跨域调用。

除此之外,Web Storage拥有setItem,getItem,removeItem,clear等方法,不像cookie需要前端开发者自己封装setCookie,getCookie。

但是Cookie也是不可以或缺的:Cookie的作用是与服务器进行交互,作为HTTP规范的一部分而存在 ,而Web Storage仅仅是为了在本地“存储”数据而生

后记

博主尽可能思路清晰的理了一遍cookie,session,localStorage,sessionStorage之间的区别和联系,希望可以帮到大家。

转载自:http://gold.xitu.io/post/582c7d330ce463006ce33838?utm_source=gold_browser_extension

dataChain

链表

前言

人生总是直向前行走,从不留下什么。

原文地址:学习javascript数据结构(二)——链表

博主博客地址:[Damonare的个人博客]http://damonare.cn/

正文

链表简介

上一篇博客-学习javascript数据结构(一)——栈和队列说了栈和队列在javascript中的实现,我们运用javascript提供的API很容易的实现了栈和队列,但这种数据结构有一个很明显的缺点,因为数组大小是固定的所以我们在移除或是添加一项数据的时候成本很高,基本都需要吧数据重排一次。(javascript的Array类方法虽然很方便但背后的原理同样是这样的)

相比数组我们今天主角——链表就要来的随性的多,简单的理解可以是这样:在内存中,栈和队列(数组)的存在就是一个整体,如果想要对她内部某一个元素进行移除或是添加一个新元素就要动她内部所有的元素,所谓牵一发而动全身;而链表则不一样,每一个元素都是由元素本身数据和指向下一个元素的指针构成,所以添加或是移除某一个元素不需要对链表整体进行操作,只需要改变相关元素的指针指向就可以了。

链表在实际生活中的例子也有很多,比如自行车的链条,环环相扣,但添加或是移除某一个环节只需要对症下药,对相关环节进行操作就OK。再比如:火车,火车就是一个链表,每一节车厢就是元素,想要移除或是添加某一节车厢,只需要把连接车厢的链条改变一下就好了。那么,在javascript中又该怎么去实现链表结构呢?

链表的创建

首先我们要创建一个链表类:

function LinkedList(){
    //各种属性和方法的声明
}

然后我们需要一种数据结构来保存链表里面的数据:

var Node=function(element){
    this.element=element;
    this.next=null;
}

//Node类表示要添加的元素,他有两个属性,一个是element,表示添加到链表中的具体的值;另一个是next,表示要指向链表中下一个元素的指针。
接下来,我们需要给链表声明一些方法:

  • append(element):向链表尾部添加一个新的元素;
  • insert(position,element):向链表特定位置插入元素;
  • remove(element):从链表移除一项;
  • indexOf(element):返回链表中某元素的索引,如果没有返回-1;
  • removeAt(position):从特定位置移除一项;
  • isEmpty():判断链表是否为空,如果为空返回true,否则返回false;
  • size():返回链表包含的元素个数;
  • toString():重写继承自Object类的toString()方法,因为我们使用了Node类;
  • 链表的完整代码:

    function LinkedList() {
    
    //Node类声明
    
    let Node = function(element){
    
    this.element = element;
    
    this.next = null;
    
        };
        //初始化链表长度
    
    let length = 0;
    
    //初始化第一个元素
    
        let head = null;
    
    this.append = function(element){
        //初始化添加的Node实例
        let node = new Node(element),
            current;
        if (head === null){
            //第一个Node实例进入链表,之后在这个LinkedList实例中head就不再是null了
            head = node;
        } else {
            current = head;
            //循环链表知道找到最后一项,循环结束current指向链表最后一项元素
            while(current.next){
                current = current.next;
            }
            //找到最后一项元素后,将他的next属性指向新元素node,j建立链接
            current.next = node;
        }
            //更新链表长度
            length++;
    };
    this.insert = function(position, element){
        //检查是否越界,超过链表长度或是小于0肯定不符合逻辑的
        if (position >= 0 && position <= length){
            let node = new Node(element),
                current = head,
                previous,
                   index = 0;
            if (position === 0){
                //在第一个位置添加
                node.next = current;
                head = node;
            } else {
                //循环链表,找到正确位置,循环完毕,previous,current分别是被添加元素的前一个和后一个元素
                while (index++ < position){
                    previous = current;
                    current = current.next;
                }
                node.next = current;
                previous.next = node;
            }
            //更新链表长度
            length++;
            return true;
           } else {
            return false;
        }
    };
    this.removeAt = function(position){
        //检查是否越界,超过链表长度或是小于0肯定不符合逻辑的
        if (position > -1 && position < length){
            let current = head,
                previous,
                index = 0;
            //移除第一个元素
            if (position === 0){
                //移除第一项,相当于head=null;
                head = current.next;
            } else {
                    //循环链表,找到正确位置,循环完毕,previous,current分别是被添加元素的前一个和后一个元素
                    while (index++ < position){
                    previous = current;
                    current = current.next;
                }
                //链接previous和current的下一个元素,也就是把current移除了
                previous.next = current.next;
            }
            length--;
            return current.element;
        } else {
            return null;
        }
    };
    this.indexOf = function(element){
        let current = head,
        index = 0;
    //循环链表找到元素位置
        while (current) {
            if (element === current.element) {
                    return index;
            }
            index++;
            current = current.next;
        }
        return -1;
    };
    this.remove = function(element){
        //调用已经声明过的indexOf和removeAt方法
        let index = this.indexOf(element);
        return this.removeAt(index);
    };
    this.isEmpty = function() {
            return length === 0;
    };
    this.size = function() {
            return length;
       };
    this.getHead = function(){
        return head;
    };
    this.toString = function(){
        let current = head,
            string = '';
        while (current) {
            string += current.element + (current.next ? ', ' : '');
            current = current.next;
           }
        return string;
    };
    this.print = function(){
        console.log(this.toString());
        };
    }//
    

一个实例化后的链表,里面是添加的数个Node类的实例

ES6版本:

    let LinkedList2 = (function () {
class Node {
    constructor(element){
        this.element = element;
        this.next = null;
    }
}
//这里我们使用WeakMap对象来记录长度状态
const length = new WeakMap();
const head = new WeakMap();
class LinkedList2 {
    constructor () {
        length.set(this, 0);
        head.set(this, null);
    }
    append(element) {
        let node = new Node(element),
            current;
        if (this.getHead() === null) {
            head.set(this, node);
        } else {
            current = this.getHead();
            while (current.next) {
                current = current.next;
            }
            current.next = node;
        }
        let l = this.size();
        l++;
        length.set(this, l);
    }
    insert(position, element) {
        if (position >= 0 && position <= this.size()) {

            let node = new Node(element),
                current = this.getHead(),
                previous,
                index = 0;
            if (position === 0) {
                node.next = current;
                head.set(this, node);
            } else {
                while (index++ < position) {
                    previous = current;
                    current = current.next;
                }
                node.next = current;
                previous.next = node;
            }
            let l = this.size();
            l++;
            length.set(this, l);
            return true;
        } else {
            return false;
        }
    }
    removeAt(position) {
        if (position > -1 && position < this.size()) {
            let current = this.getHead(),
                previous,
                index = 0;
            if (position === 0) {
                head.set(this, current.next);
            } else {
                while (index++ < position) {
                    previous = current;
                    current = current.next;
                }
                previous.next = current.next;
            }
            let l = this.size();
            l--;
            length.set(this, l);
            return current.element;
        } else {
            return null;
        }
    }
    remove(element) {
        let index = this.indexOf(element);
        return this.removeAt(index);
    }
    indexOf(element) {
        let current = this.getHead(),
            index = 0;
        while (current) {
            if (element === current.element) {
                return index;
            }
            index++;
            current = current.next;
        }
        return -1;
    }
    isEmpty() {
        return this.size() === 0;
    }
    size() {
        return length.get(this);
    }
    getHead() {
        return head.get(this);
    }
    toString() {
        let current = this.getHead(),
            string = '';
        while (current) {
            string += current.element + (current.next ? ', ' : '');
            current = current.next;
        }
        return string;

    }
    print() {
        console.log(this.toString());
    }
}
return LinkedList2;
})();

双向链表

function DoublyLinkedList() {
let Node = function(element){
    this.element = element;
    this.next = null;
    this.prev = null; //NEW
};
let length = 0;
let head = null;
let tail = null; //NEW
this.append = function(element){
    let node = new Node(element),
        current;
    if (head === null){
        head = node;
        tail = node; //NEW
    } else {
        //NEW
        tail.next = node;
        node.prev = tail;
        tail = node;
    }
    length++;
};
this.insert = function(position, element){
    if (position >= 0 && position <= length){
        let node = new Node(element),
            current = head,
            previous,
            index = 0;
        if (position === 0){
            if (!head){       //NEW
                head = node;
                tail = node;
            } else {
                node.next = current;
                current.prev = node; //NEW
                head = node;
            }
        } else  if (position === length) { ////NEW
            current = tail;   
            current.next = node;
            node.prev = current;
            tail = node;
        } else {
            while (index++ < position){
                previous = current;
                current = current.next;
            }
            node.next = current;
            previous.next = node;
            current.prev = node; //NEW
            node.prev = previous; //NEW
        }
        length++;
        return true;
    } else {
        return false;
    }
};
this.removeAt = function(position){
    if (position > -1 && position < length){
        let current = head,
            previous,
            index = 0;
        if (position === 0){ //NEW
            if (length === 1){ //
                tail = null;
            } else {
                head.prev = null;
            }
        } else if (position === length-1){  //NEW
            current = tail;
            tail = current.prev;
            tail.next = null;
        } else {
            while (index++ < position){
                previous = current;
                current = current.next;
            }
            previous.next = current.next;
            current.next.prev = previous; //NEW
        }
        length--;
        return current.element;
    } else {
        return null;
    }
};
this.remove = function(element){
    let index = this.indexOf(element);
    return this.removeAt(index);
};
this.indexOf = function(element){
    let current = head,
        index = -1;
    if (element == current.element){
        return 0;
    }
    index++;
    while(current.next){
        if (element == current.element){
            return index;
        }
        current = current.next;
        index++;
    }
    //check last item
    if (element == current.element){
        return index;
    }
    return -1;
};
this.isEmpty = function() {
    return length === 0;
};
this. size = function() {
    return length;
};
this.toString = function(){
    let current = head,
        s = current ? current.element : '';
    while(current && current.next){
        current = current.next;
        s += ', ' + current.element;
    }
    return s;
};
this.inverseToString = function() {
    let current = tail,
        s = current ? current.element : '';
    while(current && current.prev){
        current = current.prev;
        s += ', ' + current.element;
    }
    return s;
};
this.print = function(){
    console.log(this.toString());
};
this.printInverse = function(){
    console.log(this.inverseToString());
};
this.getHead = function(){
    return head;
};
this.getTail = function(){
    return tail;
}
}

双向链表和单项比起来就是Node类多了一个prev属性,也就是每一个node不仅仅有一个指向它后面元素的指针也有一个指向它前面的指针。

循环链表

明白了前面的基础链表和双向链表之后这个肯定不在话下了,循环,其实就是整个链表实例变成了一个圈,在单项链表中最后一个元素的next属性为null,现在让它指向第一个元素也就是head,那么他就成了单向循环链表。在双向链表中最后一个元素的next属性为null,现在让它指向第一个元素也就是head,那么他就成了双向循环链表。就那么回事...

javascript

JavaScript 知识点整理

JavaScript是按照ECMAScript标准设计和实现的,后文说的JavaScript语法其实是ES5的标准的实现。
先说说有哪些基础语法?

最基础语法有哪些?

基础语法几乎所有的语言差异不大,无非数据类型、操作符、控制语句、函数等,简单列举下。

5种基本数据类型 & 1种复杂的数据类型

JavaScript包含5种基本数据类型,分别是undefined / null / boolean / number / string,基本数据类型就这五种,没有其他的!
JavaScript包含1种复杂的数据类型,就是Object类型,Object类型是所有其他对象的基类。
注意:JavaScript并不区分浮点数和整数,都是用number来表示。

前面提到的5种基本数据类型,以及这儿的1种复杂数据类型,这就是数据类型的全部了!

基本操作符

这个是常识,知道怎么回事就好。
常用的操作符包括:算术操作符、关系操作符、布尔操作符、赋值操作符等。

控制语句

这就是我们常说的if-else之类的控制语句。
常用的并不多:if语句、switch语句、for语句、while语句、for-in语句。

函数

函数就是一小段逻辑的封装,理论上逻辑越独立越好。
JavaScript函数相对其他语言来说有很大不同。JavaScript函数既可以作为参数,也可以作为返回值。
此外JavaScript函数可以接受任意数量的参数,并且可以通过arguments对象来访问这些参数。

任何一门语言的基础语法都是相通的,除开一些细节差异,大致就是上面这些了:数据类型、操作符、控制语句、函数、模块等等。

接下来介绍稍微复杂的一些概念。

变量、作用域、内存问题

变量

JavaScript变量分为两种:基本类型和引用类型。其中基本类型就是前面提到的5种基本数据类型,引用类型就是前面提到的Object以及基于它的其他复杂数据类型。
✦ 基本类型:在内存中占据实际大小的空间,赋值的时候,会在内存中创建一份新的副本。保存在栈内存中。
✦ 引用类型:指向对象的指针而不是对象本身,赋值的时候,只是创建了一个新的指针指向对象。保存在堆内存中。

变量内存分配

一句话就是,基本类型在内存中是实际的值;而引用类型在内存中就是一个指针,指向一个对象,多个引用类型可能同时指向同一个对象。

那么,如何确定某个变量是哪种数据类型呢?
确定一个变量是哪种基本类型用typeof操作符。
确定一个变量是哪种引用类型用instanceof操作符。
这个别忘了!

作用域

变量是在某个特定的作用域中声明的,作用域决定了这些变量的生命周期,以及哪些代码可以访问其中的变量。
JavaScript作用域只包括全局作用域和函数作用域,并不包含块级作用域!

作用域是可以嵌套的,从而形成作用域链。由于作用域链的存在,可以让变量的查找向上追溯,即子函数可以访问父函数的作用域=>祖先函数的作用域=>直到全局作用域,这种函数我们也称为闭包,后文会介绍。

var color = "blue";

function changeColor() {
    var anotherColor = "red";

function swapColors() {
    var tempColor = anotherColor;
    anotherColor = color;
    color = tempColor;
    // 这里可以访问color、anotherColor、tempColor 
}
// 这里可以访问color、anotherColor,但不能访问tempColor
swapColors();
}
// 这里只能访问color、changeColor();

如下图所示,每个作用域能够访问到的变量以及嵌套的作用域可向上追溯。

作用域链
作用域的概念看着简单,实际使用会有不少问题,遇到问题要细心分析。

内存问题

JavaScript引擎具有自动垃圾回收机制,不需要太关注内存分配和垃圾回收问题。这儿就不展开了!

引用类型

前面提过,Object是唯一的复杂数据类型,引用类型都是从Object类型上继承而来。
✦ Array:数组类型
✦ Date:日期类型
✦ RegExp:正则表达式类型,这个多学学有好处!
✦ 等等…
那问题来了,我们用的最多的函数是什么数据类型呢?答案是Function类型!
诶,好像发现了点什么东西?由于Function是引用类型,而JavaScript又可以往引用类型上加属性和方法。那么,函数也可以!这也是JavaScript函数强大和复杂的地方。也就是说:函数也可以拥有自定义方法和属性!

此外,JavaScript对前面提到的5种基本类型的其中3种也做了引用类型封装,分别是Boolean、Number、String,但其实使用不多,了解就行。

对了,在所有代码执行之前,作用域就内置了两个对象,分别是Global和Math,其中浏览器的Global就是window啦!

到此为止,JavaScript中基础的概念都差不多介绍了,其中函数和作用域相对来说复杂一些,其他的都比较浅显。
接下来,我会介绍介绍JavaScript中一些稍微复杂一些的概念:面向对象。

面向对象编程

JavaScript本身并没有类和接口的概念了,面向对象都是基于原型实现的。
为了简单,我们只分析面向对象的两个问题:
✦ 如何定义一个类?
✦ 如何实现类的继承

定义一个类

不扯其他的,直接告诉你。我们使用构造函数+原型的方式来定义一个类。

使用构造函数创建自定义类型,然后使用new操作符来创建类的实例,但是构造函数上的方法和属性在每个示例上都存在,不能共享,于是我们引入原型来实现方法和属性的共享。

原型
最后,我们将需要共享的方法和属性定义在原型上,把专属于实例的方法和属性放到构造函数中。到这儿,我们就通过构造函数+原型的方式定义了一个类。

// 构造函数
function Person(name, age, job) {
    this.name = name;
       this.age = age;
    this.job = job;
    this.friends = ["Shelby", "Court"];

}
// 原型
Person.prototype = {
    constructor: Person,
    sayName: function() {
        return this.name;
    }
}
// 实例化
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");

person1.friends.push("Van");
alert(person1.friends);                     //输出"Shelby,Count,Van"
alert(person2.friends);                     //输出"Shelby,Count"
alert(person1.friends === person2.friends);        //输出false
alert(person1.sayName === person2.sayName);        //输出    true

实现继承

前文讲了如何定义一个类,那么我们定义一个父类,一个子类。
如何让子类继承父类呢?不扯别的,直接告诉你。JavaScript通过原型链来实现继承!
如何构建原型链呢?将父类实例赋值给子类构造函数的原型即可。好绕,但是千万得记住了!

原型链继承
构建原型链之后,子类就可以访问父类的所有属性和方法!

// 父类
function SuperType() {
    this.property = true;
}
SuperType.prototype.getSuperValue = function() {
    return this.property;
};

// 子类
function SubType() {
    this.subproperty = false;
}

//子类继承父类
SubType.prototype = new SuperType();

//给子类添加新方法
SubType.prototype.getSubValue = function() {
    return this.subproperty;
};
//重写父类的方法
SubType.prototype.getSuperValue = function() {
    return false;
};

// 实例化
var instance = new SubType();
console.log(instance.getSuperValue()); //输出false
面向对象的知识可以用一本书来写,这儿只是简单的介绍下最基础最常用的概念。

函数表达式

JavaScript中有两种定义函数的方式:函数声明和函数表达式。
使用函数表达式无须对函数命名,从而实现动态编程,也即匿名函数。有了匿名函数,JavaScript函数有了更强大的用处。

递归

递归是一种很常见的算法,经典例子就是阶乘。也不扯其他的,直接说递归的最佳实践,上代码:

// 最佳实践,函数表达式
var factorial = (function f(num) {
    if (num <= 1) {
        return 1;
       } else {
        return num * f(num - 1);
    }
});

// 缺点:
// factorial存在被修改的可能
// 导致 return num * factorial(num - 1) 报错
function factorial(num) {
    if (num <= 1) {
        return 1;
    } else {
    return num * factorial(num - 1);
    }
}

// 缺点:
// arguments.callee,规范已经不推荐使用
function factorial(num) {
    if (num <= 1) {
    return 1;
        } else {
        return num * arguments.callee(num - 1);
    }
}

递归就是这样,好多人还在使用arguments.callee的方式,改回函数表达式的方式吧,这才是最佳实践。

啰嗦一句,好多人觉得递归难写,其实你将其分为两个步骤就会清晰很多了。
✦ 边界条件,通常是if-else。
✦ 递归调用。
按这个模式,找几个经典的递归练练手,就熟悉了。

闭包

很多人经常觉得闭包很复杂,很容易掉到坑里,其实不然。

那么闭包是什么呢?如果一个函数可以访问另一个函数作用域中的变量,那么前者就是闭包。由于JavaScript函数可以返回函数,自然,创建闭包的常用方式就是在一个函数内部创建另一个函数!
这并没有什么神奇的,在父函数中定义子函数就可以创建闭包,而子函数可以访问父函数的作用域。
我们通常是因为被闭包坑了,才会被闭包吓到,尤其是面试题里一堆闭包。

闭包的定义前面提了,如何创建闭包也说了,那么我们说说闭包的缺陷以及如何解决?

/* 我们通过subFuncs返回函数数组,然后分别调用执行 */

    // 返回函数的数组subFuncs,而这些函数对superFunc的变量有引用
// 这就是一个典型的闭包
// 那么有什么问题呢?
// 当我们回头执行subFuncs中的函数的时候,我们得到的i其实一直都是10,为什么?
// 因为当我们返回subFuncs之后,superFunc中的i=10
// 所以当执行subFuncs中的函数的时候,输出i都为10。
// 
// 以上,就是闭包最大的坑,一句话理解就是:
// 子函数对父函数变量的引用,是父函数运行结束之后的变量的状态
function superFunc() {
    var subFuncs = new Array();
    for (var i = 0; i < 10; i++) {
    subFuncs[i] = function() {
        return i;
    };
    }

return subFuncs;
}

// 那么,如何解决上诉的闭包坑呢?
// 其实原理很简单,既然闭包坑的本质是:子函数对父函数变量的引用,是父函数运行结束之后的变量的状态
// 那么我们解决这个问题的方式就是:子函数对父函数变量的引用,使用运行时的状态
// 如何做呢?
// 在函数表达式的基础上,加上自执行即可。
function superFunc() {
var subFuncs = new Array();
for (var i = 0; i < 10; i++) {
    subFuncs[i] = function(num) {
        return function() {
            return num;
        };
    }(i);
}
return subFuncs;
}

综上,闭包本身不是什么复杂的机制,就是子函数可以访问父函数的作用域。
而由于JavaScript函数的特殊性,我们可以返回函数,如果我们将作为闭包的函数返回,那么该函数引用的父函数变量是父函数运行结束之后的状态,而不是运行时的状态,这便是闭包最大的坑。而为了解决这个坑,我们常用的方式就是让函数表达式自执行。
此外,由于闭包引用了祖先函数的作用域,所以滥用闭包会有内存问题。

好像把闭包说得一无是处,那么闭包有什么用处呢?
主要是封装吧…

封装

闭包可以封装私有变量或者封装块级作用域。
➙ 封装块级作用域
JavaScript并没有块级作用域的概念,只有全局作用域和函数作用域,那么如果想要创建块级作用域的话,我们可以通过闭包来模拟。
创建并立即调用一个函数,就可以封装一个块级作用域。该函数可以立即执行其中的代码,内部变量执行结束就会被立即销毁。

function outputNumbers(count) {
// 在函数作用域下,利用闭包封装块级作用域
// 这样的话,i在外部不可用,便有了类似块级作用域
(function() {
    for (var i = 0; i < count; i++) {
        alert(i);
    }
})();

alert(i); //导致一个错误! 
}

// 在全局作用域下,利用闭包封装块级作用域
// 这样的话,代码块不会对全局作用域造成污染
(function() {
var now = new Date();

if (now.getMonth() == 0 && now.getDate() == 1) {
    alert("Happy new year!");
}
})();

// 是的,封装块级作用域的核心就是这个:函数表达式 + 自执行!
(function() {
//这里是块级作用域
})();

➙ 封装私有变量
JavaScript也没有私有变量的概念,我们也可以使用闭包来实现公有方法,通过隐藏变量暴露方法的方式来实现封装私有变量。

(function() {
//私有变量和私有函数
var privateVariable = 10;

function privateFunction() {
    return false;
}

//构造函数
MyObject = function() {};
//公有/特权方法
MyObject.prototype.publicMethod = function() {
    privateVariable++;

    return privateFunction();
};
})();

总结说点啥?

这差不多就是JavaScript的一些基础语法和稍微高级一些的用法,其实所谓的高级,都是JavaScript“不太成熟”的表现,尤其是面向对象,出于工程化的需要但是JavaScript本身并不完美支持。好在ES6最新标准解决了很多问题,结合Babel用起来也不用太考虑兼容性问题,如果你是新手的话,建议你直接去撸ES6+Babel吧。

✦ JavaScript的基础主要包括:5中基本数据类型、1种复杂的数据类型、操作符、控制语句、函数等。
✦ 了解基本的语法后,你还需要学习学习JavaScript的变量、作用域、作用域链。
✦ 常见的引用类型可以边查边用。作为过来人,建议多学学正则,对你的代码功底会有较大的提升。
✦ 面向对象编程的部分外面有很多种方式,你只需要记住使用构造函数+原型去定义一个类,使用原型链去实现继承即可。更多的扩展,去翻翻书吧。
✦ 函数表达式引出了几个比较好玩的东西:递归、闭包、封装。记住递归的最佳实践、闭包的定义及缺陷、闭包的适用场景。

JavaScript作为一门动态语言,和其他语言有较大的差异,这也造成很多人学习JavaScript时会觉得难学。但你现在看看前文,虽然是一个简略的总结,但JavaScript主要的内容就这些了,所以不要被自己吓到了。
再补一句,如果你是新手的话,建议你直接去撸ES6+Babel吧。

文/齐修_qixiuss(简书作者)
原文链接:http://www.jianshu.com/p/66f3aef3e131
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。

SubmitText

#Sublime Text 3插件安装
作为前端人员,要找一个很顺手的编辑器真的不容易,以前我用同事推荐的netbeans,很好用,但是它主要是用于php开发,且软件太大,运行起来比较慢,后来又用前端开发的webStrom,也不错,很好用,他的历史记录等功能比较强大,但是还是有一个缺点,就是软件有点大,运行起来有点慢。

我在我向大家推荐一款实用的前端开发神器,不但占地小,且插件很多,很强大。下面我向大家介绍一下它的安装及插件的使用方法。

一、安装及安装emmet插件

首先,去sublime官网下载软件:http://www.sublimetext.com/

软件很小,我用的是最新版的text3,大家可以用目前稳定版text2。打开它的官网,我们就可以看到几个动画,演示sublime的强大功能。

其次,软件安装好了之后,我们来安装一个插件,推荐使用package control组件来安装插件,很方便。

安装方法如下:

按快捷键ctrl+~ 调出命名控制行:然后如果是text2输入如下命令:

import urllib2,os,hashlib; h = '7183a2d3e96f11eeadd761d777e62404' + 'e330c659d4bb41d3bdf022e94cab3cd0'; pf = 'Package Control.sublime-package'; ipp = sublime.installed_packages_path(); os.makedirs( ipp ) if not os.path.exists(ipp) else None; urllib2.install_opener( urllib2.build_opener( urllib2.ProxyHandler()) ); by = urllib2.urlopen( 'http://sublime.wbond.net/' + pf.replace(' ', '%20')).read(); dh = hashlib.sha256(by).hexdigest(); open( os.path.join( ipp, pf), 'wb' ).write(by) if dh == h else None; print('Error validating download (got %s instead of %s), please try manual install' % (dh, h) if dh != h else 'Please restart Sublime Text to finish installation')

如果是text3输入如下命令:

import urllib.request,os,hashlib; h = '7183a2d3e96f11eeadd761d777e62404' + 'e330c659d4bb41d3bdf022e94cab3cd0'; pf = 'Package Control.sublime-package'; ipp = sublime.installed_packages_path(); urllib.request.install_opener( urllib.request.build_opener( urllib.request.ProxyHandler()) ); by = urllib.request.urlopen( 'http://sublime.wbond.net/' + pf.replace(' ', '%20')).read(); dh = hashlib.sha256(by).hexdigest(); print('Error validating download (got %s instead of %s), please try manual install' % (dh, h)) if dh != h else open(os.path.join( ipp, pf), 'wb' ).write(by)

具体安装您也可以查看:https://sublime.wbond.net/installation#st3

安装好了之后,在Preferences会看到package control,如下如:

用Package Control安装插件的方法:

  • 按下Ctrl+Shift+P调出命令面板
  • 输入install 调出 Install Package 选项并回车,然后在列表中选中要安装的插件!很简单,sublime下面有很多插件,一般编辑器有的,sublime都会以插件的形式出现,下面我们最先介绍Emmet

打开package control 输入install package 然后找到emmet,点击安装,重启sublime就可以了,具体请看:https://github.com/sergeche/emmet-sublime#readme

sublime Emmet的用法请点击!

第二,sublime常用插件:

ZenCoding

不得不用的一款前端开发方面的插件,Write less , show more.安装后可直接使用,Tab键触发,Alt+Shift+W是个代码机器。

Alignment

代码对齐,如写几个变量,选中这几行,Ctrl+Alt+A,哇,齐了。

Prefixr

写 CSS可自动添加 -webkit 等私有词缀,Ctrl+Alt+X触发。

Tag

Html格式化,右键Auto-Format Tags on Ducument。一般是用ctrl +Alt +F 触发,若触发不了,查看是不是html文件,是否选中,是否有快捷键冲突!

Clipboard History

剪贴板历史记录,显示更多历史复制,Ctrl+Shift+V触发。

SideBarEnhancements

侧栏右键功能增强,非常实用

Theme – Soda

完美的编码主题,用过的都说好,Setting user里面添加”theme”: “Soda Dark.sublime-theme”

GBK to UTF8

将文件编码从GBK转黄成UTF8,菜单 – File里面找

SFTP

直接编辑 FTP 或 SFTP 服务器上的文件,绝对FTP浮云

WordPress

集成一些WordPress的函数,对于像我这种经常要写WP模版和插件的人特别有用

PHPTools

整理排版PHP代码

YUI Compressor

压缩JS和CSS文件

Ctags

函数跳转,我的电脑上是Alt+点击 函数名称,会跳转到相应的函数

Doc​Blockr

注释插件,生成幽美的注释。标准的注释,包括函数名、参数、返回值等,并以多行显示,省去手动编写。

ftpsync

FTP ssh上传配置,安装成功配置一下host等就可以了!
Placeholders

故名思意,占位用,包括一些占位文字和HTML代码片段,实用。
JS Format

一个JS代码格式化插件。
jQuery Package for sublime Text

如果你离不开jQuery的话,这个必备~~
`Sublime Prefixr

Prefixr,CSS3 私有前缀自动补全插件,显然也很有用哇`

参考网站:http://www.haorooms.com/post/sublime_use

eventLoop

事件循环

Node.js 事件循环一: 浅析

理解事件循环系列第一步 浅析和总览

多数的网站不需要大量计算,程序花费的时间主要集中在磁盘 I/O 和网络 I/O 上面

SSD读取很快,但和CPU处理指令的速度比起来也不在一个数量级上,而且网络上一个数据包来回的时间更慢:

一个数据包来回的延迟平均320ms(我网速慢,ping国内网站会更快),这段时间内一个普通 cpu 执行几千万个周期应该没问题

因此异步IO就要发挥作用了,比如用多线程,如果用 Java 去读一个文件,这是一个阻塞的操作,在等待数据返回的过程中什么也干不了,因此就开一个新的线程来处理文件读取,读取操作结束后再去通知主线程。

这样虽然行得通,但是代码写起来比较麻烦。像 Node.js V8 这种无法开一个线程的怎么办?

先看下面函数执行过程

栈 Stack

当我们调用一个函数,它的地址、参数、局部变量都会压入到一个 stack 中

function fire() {
    const result = sumSqrt(3, 4)
    console.log(result);
}
function sumSqrt(x, y) {
    const s1 = square(x)
    const s2 = square(y)
    const sum = s1 + s2;
    return Math.sqrt(sum)
}
function square(x) {
       return x * x;
}

fire()
1
下面的图都是用 keynote 做的 keynote地址

函数 fire 首先被调用

fire 调用 sumSqrt 函数 参数为3和4

之后调用 square 参数为 x, x==3

当 square 执行结束返回时,从 stack 中弹出,并将返回值赋值给 s1
s1加入到 sumSqrt 的 stack frame 中

以同样的方式调用下一个 square 函数


在下一行的表达式中计算出 s1+s2 并赋值给 sum

之后调用 Math.sqrt 参数为sum

现在就剩下 sumSqrt 函数返回计算结果了

返回值赋值给 result


在 console 中打印出 result

最终 fire 没有任何返回值 从stack中弹出 stack也清空了

当函数执行完毕后本地变量会从 stack 中弹出,这只有在使用 numbers string boolean 这种基本数据类型时才会发生。而对象、数组的值是存在于 heap(堆) 中的,stack 只存放了他们对应的指针。

当函数之行结束从 stack 中弹出来时,只有对象的指针被弹出,而真正的值依然存在 heap 中,然后由垃圾回收器自动的清理回收。

事件循环

通过一个例子来了解函数的执行顺序

'use strict'

const express = require('express')
const superagent = require('superagent')
const app = express()

app.get('/', getArticle)

function getArticle(req, res) {
       fetchArticle(req, res)
    print()
}

const aids = [4564824, 4506868, 4767667, 4856099, 7456996];

function fetchArticle(req, res) {
    const aid = aids[Math.floor(Math.random() * aids.length)]
    superagent.get(`http://news-at.zhihu.com/api/4/news/${aid}`)
        .end((err, res) => {
            if(err) {
                console.log('error ......');
                return res.status(500).send('an error ......')
                }
            const article = res.body
            res.send(article)
            console.log('Got an article')
        })

    console.log('Now is fetching an article')
}

function print(){
    console.log('Print something')
}


app.listen('5000')

请求 http://localhost:5000/ 后打印出

Now is fetching an article

Print something

Got an article

虽然 V8 是单线程的,但底层的 C++ API 却不是。这意味着当我们执行一些非阻塞的操作,Node会调用一些代码,与引擎里的js代码同时执行。一旦这个隐藏的线程收到了等待的返回值或者抛出一个异常,之前提供的回调函数就会执行。

上面的说的Node调用的一些代码其实就是 libuv,一个开源的跨平台的异步 I/O 。最初就是为 Node.js 开发的,现在很多项目都在用

任务队列

javascript 是单线程事件驱动的语言,那我们可以给时间添加监听器,当事件触发时,监听器就能执行回调函数。

当我们去调用 setTimeout http.get fs.readFile, Node.js 会把这些定时器、http、IO操作发送给另一个线程以保证V8继续执行我们的代码。

然而我们只有一个主线程和一个 call-stack ,这样当一个读取文件的操作还在执行时,有一个网络请求request过来,那这时他的回调就需要等stack变空才能执行。

回调函数正在等待轮到自己执行所排的队就被称为任务队列(或者事件队列、消息队列)。每当主线程完成前一个任务,回调函数就会在一个无限循环圈里被调用,因此这个圈被称为事件循环。

我们前面那个获取文章的例子的执行顺序就会如下:

  1. express 给 request 事件注册了一个 handler,并且当请求到达路径 ‘/‘ 时来触发handler
  2. 调过各个函数并且在端口 5000 上启动监听
  3. stack 为空,等待 request 事件触发
  4. 根据传入的请求,事件触发,express 调用之前提供的函数getArticle
  5. getArticle 压入(push) stack
  6. fetchArticle 被调用 同时压入 stack
  7. Math.floorMath.random 被调用压入 stack 然后再 弹出(pop), 从 aids 里面取出的一个值被赋值给变量 aid
  8. superagent.get 被执行,参数为 'http://news-at.zhihu.com/api/4/news/${aid}' ,并且回调函数注册给了 end 事件
  9. http://news-at.zhihu.com/api/4/news/${aid} 的HTTP请求被发送到后台线程,然后函数继续往下执行
  10. 'Now is fetching an article' 打印在 console 中。 函数 fetchArticle 返回
  11. print 函数被调用, 'Print something' 打印在 console
  12. 函数 getArticle 返回,并从 stack 中弹出, stack 为空
  13. 等待 http://news-at.zhihu.com/api/4/news/${aid} 发送相应信息
  14. 响应信息到达,end 事件被触发
  15. 注册给 end 事件的匿名回调函数被执行,这个匿名函数和他闭包中的所有变量压入 stack,这意味着这个匿名函数可以访问并修改 express, superagent, app,aids, req, res, aid的值以及之前所有已经定义的函数
  16. 函数 res.send() 伴随着 200 或 500 的状态码被执行,但同时又被放入到后台线程中,因此 响应流 不会阻塞我们函数的执行。匿名函数也被 pop 出 stack。

Microtasks Macrotasks


任务队列不止一个,还有 microtasks 和 macrotasks

microtasks:
  • process.nextTick
  • promise
  • Object.observe
macrotasks:
  • setTimeout
  • setInterval
  • setImmediate
  • I/O
    这两个的详细区别下一篇再写,先看一段代码

    console.log(‘start’)

    const interval = setInterval(() => {

    console.log('setInterval')
    

    }, 0)

    setTimeout(() => {

    console.log('setTimeout 1')
     Promise.resolve()
     .then(() => {
       console.log('promise 3')
     })
     .then(() => {
       console.log('promise 4')
     })
     .then(() => {
       setTimeout(() => {
             console.log('setTimeout 2')
         Promise.resolve()
             .then(() => {
               console.log('promise 5')
             })
             .then(() => {
               console.log('promise 6')
             })
             .then(() => {
               clearInterval(interval)
             })
       }, 0)
     })
    

    }, 0)

    Promise.resolve()

    .then(() => {  
        console.log('promise 1')
    })
    .then(() => {
            console.log('promise 2')
    })
    

理解了node的事件循环还是比较容易得出答案的:

start
promise 1
promise 2
setInterval
setTimeout 1
promise 3
promise 4
setInterval
setTimeout 
promise 5
promise 6

根据 WHATVG 的说明,在一个事件循环的周期(cycle)中一个 (macro)task 应该从 macrotask 队列开始执行。当这个 macrotask 结束后,所有的 microtasks 将在同一个 cycle 中执行。在 microtasks 执行时还可以加入更多的 microtask,然后一个一个的执行,直到 microtask 队列清空。

规范理解起来有点晦涩,来看下上面的例子

Cycle 1

1) setInterval 被列为 task

2) setTimeout 1 被列为 task

3) Promise.resolve 1 中两个 then 被列为 microtask

4) stack 清空 microtasks 执行

任务队列: setInterval setTimeout 1

Cycle 2

5) microtasks 队列清空 setInteval 的回调可以执行。另一个 setInterval 被列为 task , 位于 setTimeout 1 后面

任务队列: setTimeout 1 setInterval

Cycle 3

6) microtask 队列清空,setTimeout 1 的回调可以执行,promise 3`promise 4 被列为microtasks`

7) promise 3promise 4 执行。 setTimeout 2 被列为 task

任务队列 setInterval setTimeout 2

Cycle 4

8) microtask 队列清空 setInteval 的回调可以执行。然后另一个 setInterval 被列为 task ,位于 setTimeout 2 后面

任务队列: setTimeout 2 setInterval

9) setTimeout 2的回调执行,promise 5promise 6 被列为 microtasks

现在 promise 5promise 6 的回调应该执行,并且 clear 掉 interval。 但有的时候不知道为什么 setInterval 还会在执行一遍,变成下面结果

...
setTimeout 2
setInterval
promise 5
promise 6

但是把上面的代码放入 chrome console 中执行却没有问题。这一点还要再根据不同的 node版本 查一下。

引自:https://github.com/ccforward/cc/issues/47

react

#React入门
React入门分享PPT下载
React 起源于 Facebook 的内部项目,因为该公司对市场上所有 JavaScript MVC 框架,都不满意,就决定自己写一套,用来架设Instagram 的网站。由于 React 的设计思想极其独特,属于革命性创新,性能出众,代码逻辑却非常简单。React的虚拟DOM的特性使得他可以多端渲染,甚至直接渲染到Canvas。

##框架特色

  • 类似于web component的组件封装,面向未来
  • 可复用,可组合的组件架构
  • 数据驱动,UI自动更新,解放DOM操作
  • JSX语法使得代码看起来简洁清晰
  • 单向数据流: 使得组件行为更可预测
  • Virtual DOM: 虚拟DOM的抽象使得React 组件可以跨端渲染
  • React Native App开发
  • 至今未发1.0,API有可能有较大改变

##如何使用:babel

  • react.js:react的核心库
  • react-dom.js:提供与DOM相关的功能
  • Jsx:【HTML 语言直接写在 JavaScript 语言之中,不加任何引号】需要babel编译

    • 浏览器端使用babel browser.js,由于编译过程比较慢,一般仅在开发时候使用
      * 也可使用babel预编译,在发布时完成编译
1
2
3
4
5
6
7
8
9
10
11
12
babel src —out-dir build
```
* JSX 的基本语法规则:HTML 语言直接写在 JavaScript 语言之中,不加任何引号,这就是JSX语法。遇到 HTML 标签(以 < 开头),就用 HTML 规则解析;遇到代码块(以 { 开头),就用 JavaScript 规则解析
* 在javascript中写XML,不是模板引擎 * HTML元素必须用一个元素包裹
* 默认进行字符转义,防XSS攻击(dangerouslySetInnerHTML)
* ‘{}’包裹js表达式:简单变量,数组
* 分支:预定义变量,三元表达式,自执行函数
* 数组循环:arr.map(function() {})
* babel JSX在线编译器
## 组件
* 组件声明
var HelloMessage = React.createClass({});
1
2
* 组件必须有自己的render方法
* 组件使用:模板中直接作为标签名(标签必须闭合,自闭合和配套闭合都可以)使用,自定义标签必须大写


// 或者


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
* 通过 `this.props`可访问标签上定义的所有属性
注意:`class` 属性需要写成 `className` ,`for` 属性需要写成 `htmlFor` ,这是因为 `class` 和 `for` 是 JavaScript 的保留字
* `this.props.children` 属性表示组件的所有子节点[array | object | string | undefined],因此可直接使用React.Children提供的各种工具方法来操作,如map
// 使用React.Children提供的工具方法使得我们可以不去考虑 children的数据类型
React.Children.map(this.props.children, function(child) {
});
* PropTypes:
组件的属性可以接受任意值,字符串、对象、函数等等都可以。有时,我们需要一种机制,验证别人使用组件时,提供的参数是否符合要求。
组件类的PropTypes属性,就是用来验证组件实例的属性是否符合要求
var MyTitle = React.createClass({
propTypes: {
title: React.PropTypes.string.isRequired,
},
render: function() {
return <h1> {this.props.title} </h1>;
}
});
更多PropTypes验证可参考官方文档。
* getDefaultProps:
此方法可用于设置组件属性的默认值。
* ref:
用于获取真实的DOM节点。获取用户的输入。这时就必须获取真实的 DOM 节点,虚拟 DOM 是拿不到用户输入的。为了做到这一点,文本输入框必须有一个 ref 属性,然后 this.refs.[refName] 就会返回这个真实的 DOM 节点。
* 事件
React 组件支持很多事件,除了 Click 事件以外,还有 KeyDown 、Copy、Scroll 等,完整的事件清单请查看官方文档。所有事件都在冒泡阶段触发。
* state: getInitialState, this.setState
可将组件看做一个状态机,一开始有一个初始状态,然后用户互动,导致状态变化,从而触发重新渲染 UI。
当用户点击组件,导致状态变化,this.setState 方法就修改状态值,每次修改以后,自动调用 this.render 方法,再次渲染组件。
* 表单交互:
* value, checked, selected,使得组件受限,必须通过onChage事件改
变值
* 仅仅设置默认值可以用:defaultValue, defaultChecked, defaultValue
* 组件样式style写法

style={{opacity: this.state.opacity}}

```
这是因为 React 组件样式是一个对象,所以第一重大括号表示这是 JavaScript 语法,第二重大括号表示样式对象。

  • 大写就是自定义的组件,小写就是react内部的dom组件
  • 组件生命周期

组件并不是真实的 DOM 节点,而是存在于内存之中的一种数据结构,叫做虚拟 DOM (virtual DOM)。只有当它插入文档以后,才会变成真实的 DOM 。根据 React 的设计,所有的 DOM 变动,都先在虚拟 DOM 上发生,然后再将实际发生变动的部分,反映在真实 DOM上,这种算法叫做 DOM diff ,它可以极大提高网页的性能表现。

  • 生命周期的三个状态

    • Mounting: 已插入真实 DOM
    • Updating: 正在被重新渲染
    • Unmounting: 已移出真实 DOM
  • React为每个状态提供两种处理函数,will(进入状态前调用)/did(进入状态后调用),共5个处理函数
    • componentWillMount()
    • componentDidMount()
    • componentWillUpdate(object nextProps, object nextState)
    • componentDidUpdate(object prevProps, object prevState)
    • componentWillUnmount()
  • 此外,React 还提供两种特殊状态的处理函数。
    • componentWillReceiveProps(object nextProps):已加载组件收到新的参数时调用
    • shouldComponentUpdate(object nextProps, object nextState):组件判断是否重新渲染时调用
  • mixins(array): 定义复杂组件间的共用功能。关于 mixin 值得一提的优点是,如果一个组件使用了多个 mixin,并用有多个 mixin 定义了同样的生命周期方法(如:多个 mixin 都需要在组件销毁时做资源清理操作),所有这些生命周期方法都保证会被执行到。方法执行顺序是:首先按 mixin 引入顺序执行 mixin 里方法,最后执行组件内定义的方法。
  • setState是异步操作

    this.setState(

    function(state, props) | object state,
    [function callback]
    

    )

  • 组件通信

    • 父子组件通信:
      子组件通过props可以访问父组件的属性和方法
    • 非父子组件通信:
      使用全局事件 Pub/Sub 模式,在 componentDidMount 里面订阅事件,在 componentWillUnmount 里面取消订阅,当收到事件触发的时候调用 setState 更新 UI。这种模式在复杂的系统里面可能会变得难以维护,对于比较复杂的应用,推荐使用类似 Flux 这种单项数据流架构。
  • JSX语法: 仅仅是方法和对象的语法糖

  • 渲染DOM时元素属性会做过滤,只会部分属性,如className, id, data-*…当然组件中的this.props能拿到所有属性

  • 变量取值
  • 分支
    没有if语句,可以用三元元算符,也可以在componentWillMounting函数中定义变量来实现,但可以在自执行函数里面写。

    {function(){
    if (true){ 
    return <h3>true 分支</h3>
        } else {
        return <h3>false 分支</h3>
    }
    }()}
    
  • 循环
    数组循环用.map;对象循环只能用自执行函数了
    参考:HTML扩展
    {…this.props}会把父组件的所以props赋值到子组件上

    辅助

  • Flux
    React 标榜自己是 MVC 里面 V 的部分,那么 Flux 就相当于添加 M 和 C 的部分。
    Flux 是 Facebook 使用的一套前端应用的架构模式。
    实现库:Facebook, Flux, Redux, Reflux
    Flux-demos
  • chrom插件:React Developer Tools
  • sublime 使用JSX
  • 官方文档(中文没有入口,可自行添加-zh-CN查看)
  • react-demos
  • React入门实例教程

expressSession

express-session

一、什么是session?

最近在学习node.js 的express框架,接触到了关于session方面的内容。翻阅了一些的博客,学到了不少东西,发现一篇博文讲的很好,概念内容摘抄如下:

Session是什么

Session一般译作会话,牛津词典对其的解释是进行某活动连续的一段时间。从不同的层面看待session,它有着类似但不全然相同的含义。比如,在web应用的用户看来,他打开浏览器访问一个电子商务网站,登录、并完成购物直到关闭浏览器,这是一个会话。而在web应用的开发者开来,用户登录时我需要创建一个数据结构以存储用户的登录信息,这个结构也叫做session。因此在谈论session的时候要注意上下文环境。而本文谈论的是一种基于HTTP协议的用以增强web应用能力的机制或者说一种方案,它不是单指某种特定的动态页面技术,而这种能力就是保持状态,也可以称作保持会话。

为什么需要session

谈及session一般是在web应用的背景之下,我们知道web应用是基于HTTP协议的,而HTTP协议恰恰是一种无状态协议。也就是说,用户从A页面跳转到B页面会重新发送一次HTTP请求,而服务端在返回响应的时候是无法获知该用户在请求B页面之前做了什么的。

对于HTTP的无状态性的原因,相关RFC里并没有解释,但联系到HTTP的历史以及应用场景,我们可以推测出一些理由:

1.   设计HTTP最初的目的是为了提供一种发布和接收HTML页面的方法。那个时候没有动态页面技术,只有纯粹的静态HTML页面,因此根本不需要协议能保持状态;
2.   用户在收到响应时,往往要花一些时间来阅读页面,因此如果保持客户端和服务端之间的连接,那么这个连接在大多数的时间里都将是空闲的,这是一种资源的无端浪费。所以HTTP原始的设计是默认短连接,即客户端和服务端完成一次请求和响应之后就断开TCP连接,服务器因此无法预知客户端的下一个动作,它甚至都不知道这个用户会不会再次访问,因此让HTTP协议来维护用户的访问状态也全然没有必要;
3.   将一部分复杂性转嫁到以HTTP协议为基础的技术之上可以使得HTTP在协议这个层面上显得相对简单,而这种简单也赋予了HTTP更强的扩展能力。事实上,session技术从本质上来讲也是对HTTP协议的一种扩展。
总而言之,HTTP的无状态是由其历史使命而决定的。但随着网络技术的蓬勃发展,人们再也不满足于死板乏味的静态HTML,他们希望web应用能动起来,于是客户端出现了脚本和DOM技术,HTML里增加了表单,而服务端出现了CGI等等动态技术。

而正是这种web动态化的需求,给HTTP协议提出了一个难题:一个无状态的协议怎样才能关联两次连续的请求呢?也就是说无状态的协议怎样才能满足有状态的需求呢?

此时有状态是必然趋势而协议的无状态性也是木已成舟,因此我们需要一些方案来解决这个矛盾,来保持HTTP连接状态,于是出现了cookie和session。

对于此部分内容,读者或许会有一些疑问,笔者在此先谈两点:

1.   无状态性和长连接
可能有人会问,现在被广泛使用的HTTP1.1默认使用长连接,它还是无状态的吗?
连接方式和有无状态是完全没有关系的两回事。因为状态从某种意义上来讲就是数据,而连接方式只是决定了数据的传输方式,而不能决定数据。长连接是随着计算机性能的提高和网络环境的改善所采取的一种合理的性能上的优化,一般情况下,web服务器会对长连接的数量进行限制,以免资源的过度消耗。
2.   无状态性和session
    Session是有状态的,而HTTP协议是无状态的,二者是否矛盾呢?

Session和HTTP协议属于不同层面的事物,后者属于ISO七层模型的最高层应用层,前者不属于后者,前者是具体的动态页面技术来实现的,但同时它又是基于后者的。在下文中笔者会分析Servlet/Jsp技术中的session机制,这会使你对此有更深刻的理解。

Cookie和Session

上面提到解决HTTP协议自身无状态的方式有cookie和session。二者都能记录状态,前者是将状态数据保存在客户端,后者则保存在服务端。

首先看一下cookie的工作原理,这需要有基本的HTTP协议基础。

cookie是在RFC2109(已废弃,被RFC2965取代)里初次被描述的,每个客户端最多保持三百个cookie,每个域名下最多20个Cookie(实际上一般浏览器现在都比这个多,如Firefox是50个),而每个cookie的大小为最多4K,不过不同的浏览器都有各自的实现。对于cookie的使用,最重要的就是要控制cookie的大小,不要放入无用的信息,也不要放入过多信息。

无论使用何种服务端技术,只要发送回的HTTP响应中包含如下形式的头,则视为服务器要求设置一个cookie:
1
Set-cookie:name=name;expires=date;path=path;domain=domain
支持cookie的浏览器都会对此作出反应,即创建cookie文件并保存(也可能是内存cookie),用户以后在每次发出请求时,浏览器都要判断当前所有的cookie中有没有没失效(根据expires属性判断)并且匹配了path属性的cookie信息,如果有的话,会以下面的形式加入到请求头中发回服务端:
1
Cookie: name="zj"; Path="/linkage"
服务端的动态脚本会对其进行分析,并做出相应的处理,当然也可以选择直接忽略。

这里牵扯到一个规范(或协议)与实现的问题,简单来讲就是规范规定了做成什么样子,那么实现就必须依据规范来做,这样才能互相兼容,但是各个实现所使用的方式却不受约束,也可以在实现了规范的基础上超出规范,这就称之为扩展了。无论哪种浏览器,只要想提供cookie的功能,那就必须依照相应的RFC规范来实现。所以这里服务器只管发Set-cookie头域,这也是HTTP协议无状态性的一种体现。

需要注意的是,出于安全性的考虑,cookie可以被浏览器禁用。

再看一下session的原理:

笔者没有找到相关的RFC,因为session本就不是协议层面的事物。它的基本原理是服务端为每一个session维护一份会话信息数据,而客户端和服务端依靠一个全局唯一的标识来访问会话信息数据。用户访问web应用时,服务端程序决定何时创建session,创建session可以概括为三个步骤:

1.   生成全局唯一标识符(sessionid);
2.   开辟数据存储空间。一般会在内存中创建相应的数据结构,但这种情况下,系统一旦掉电,所有的会话数据就会丢失,如果是电子商务网站,这种事故会造成严重的后果。不过也可以写到文件里甚至存储在数据库中,这样虽然会增加I/O开销,但session可以实现某种程度的持久化,而且更有利于session的共享;
3.   将session的全局唯一标示符发送给客户端。
问题的关键就在服务端如何发送这个session的唯一标识上。联系到HTTP协议,数据无非可以放到请求行、头域或Body里,基于此,一般来说会有两种常用的方式:cookie和URL重写。
1
2
3
4
5
1. Cookie
读者应该想到了,对,服务端只要设置Set-cookie头就可以将session的标识符传送到客户端,而客户端此后的每一次请求都会带上这个标识符,由于cookie可以设置失效时间,所以一般包含session信息的cookie会设置失效时间为0,即浏览器进程有效时间。至于浏览器怎么处理这个0,每个浏览器都有自己的方案,但差别都不会太大(一般体现在新建浏览器窗口的时候);
2. URL重写
所谓URL重写,顾名思义就是重写URL。试想,在返回用户请求的页面之前,将页面内所有的URL后面全部以get参数的方式加上session标识符(或者加在path info部分等等),这样用户在收到响应之后,无论点击哪个链接或提交表单,都会在再带上session的标识符,从而就实现了会话的保持。读者可能会觉得这种做法比较麻烦,确实是这样,但是,如果客户端禁用了cookie的话,URL重写将会是首选。
到这里,读者应该明白我前面为什么说session也算作是对HTTP的一种扩展了吧。如下两幅图是笔者在Firefox的Firebug插件中的截图,可以看到,当我第一次访问index.jsp时,响应头里包含了Set-cookie头,而请求头中没有。当我再次刷新页面时,图二显示在响应中不在有Set-cookie头,而在请求头中却有了Cookie头。注意一下Cookie的名字:jsessionid,顾名思义,就是session的标识符,另外可以看到两幅图中的jsessionid的值是相同的,原因笔者就不再多解释了。另外读者可能在一些网站上见过在最后附加了一段形如jsessionid=xxx的URL,这就是采用URL重写来实现的session。

Cookie和session由于实现手段不同,因此也各有优缺点和各自的应用场景:

1.   应用场景
Cookie的典型应用场景是Remember Me服务,即用户的账户信息通过cookie的形式保存在客户端,当用户再次请求匹配的URL的时候,账户信息会被传送到服务端,交由相应的程序完成自动登录等功能。当然也可以保存一些客户端信息,比如页面布局以及搜索历史等等。
Session的典型应用场景是用户登录某网站之后,将其登录信息放入session,在以后的每次请求中查询相应的登录信息以确保该用户合法。当然还是有购物车等等经典场景;
2.   安全性
cookie将信息保存在客户端,如果不进行加密的话,无疑会暴露一些隐私信息,安全性很差,一般情况下敏感信息是经过加密后存储在cookie中,但很容易就会被窃取。而session只会将信息存储在服务端,如果存储在文件或数据库中,也有被窃取的可能,只是可能性比cookie小了太多。
Session安全性方面比较突出的是存在会话劫持的问题,这是一种安全威胁,这在下文会进行更详细的说明。总体来讲,session的安全性要高于cookie;
3.   性能
Cookie存储在客户端,消耗的是客户端的I/O和内存,而session存储在服务端,消耗的是服务端的资源。但是session对服务器造成的压力比较集中,而cookie很好地分散了资源消耗,就这点来说,cookie是要优于session的;
4.   时效性
Cookie可以通过设置有效期使其较长时间内存在于客户端,而session一般只有比较短的有效期(用户主动销毁session或关闭浏览器后引发超时);
5.   其他
Cookie的处理在开发中没有session方便。而且cookie在客户端是有数量和大小的限制的,而session的大小却只以硬件为限制,能存储的数据无疑大了太多。

二、 express-session的安装

Installation

$ npm install express-session

API

var session = require('express-session')
session(options)

Create a session middleware with the given options.

Note Session data is not saved in the cookie itself, just the session ID. Session data is stored server-side.

Note Since version 1.5.0, the cookie-parser middleware no longer needs to be used for this module to work. This module now directly reads and writes cookies on req/res. Using cookie-parser may result in issues if the secret is not the same between this module and cookie-parser.

Warning The default server-side session storage, MemoryStore, is purposely not designed for a production environment. It will leak memory under most conditions, does not scale past a single process, and is meant for debugging and developing.

For a list of stores, see compatible session stores.

Options

express-session accepts these properties in the options object.

Settings object for the session ID cookie. The default value is { path: '/', httpOnly: true, secure: false, maxAge: null }.

The following are options that can be set in this object.

Specifies the value for the Domain Set-Cookie attribute. By default, no domain is set, and most clients will consider the cookie to apply to only the current domain.

Specifies the Date object to be the value for the Expires Set-Cookie attribute. By default, no expiration is set, and most clients will consider this a “non-persistent cookie” and will delete it on a condition like exiting a web browser application.

Note If both expires and maxAge are set in the options, then the last one defined in the object is what is used.

Note The expires option should not be set directly;
instead only use the maxAge option.

cookie.httpOnly
Specifies the boolean value for the HttpOnly Set-Cookie attribute. When truthy, the HttpOnly attribute is set, otherwise it is not. By default, the HttpOnly attribute is set.

Note be careful when setting this to true, as compliant clients will not allow client-side JavaScript to see the cookie in document.cookie.

Specifies the number (in milliseconds) to use when calculating the Expires Set-Cookie attribute. This is done by taking the current server time and adding maxAge milliseconds to the value to calculate an Expires datetime. By default, no maximum age is set.

Note If both expires and maxAge are set in the options, then the last one defined in the object is what is used.

Specifies the value for the Path Set-Cookie. By default, this is set to ‘/‘, which is the root path of the domain.

Specifies the boolean or string to be the value for the SameSite Set-Cookie attribute.

  • true will set the SameSite attribute to Strict for strict same site enforcement.
  • false will not set the SameSite attribute.
  • ‘lax’ will set the SameSite attribute to Lax for lax same site enforcement.
  • ‘strict’ will set the SameSite attribute to Strict for strict same site enforcement.
    More information about the different enforcement levels can be found in the specification https://tools.ietf.org/html/draft-west-first-party-cookies-07#section-4.1.1

NoteThis is an attribute that has not yet been fully standardized, and may change in the future. This also means many clients may ignore this attribute until they understand it.

Specifies the boolean value for the Secure Set-Cookieattribute. When truthy, the Secure attribute is set, otherwise it is not. By default, the Secure attribute is not set.

Note be careful when setting this to true, as compliant clients will not send the cookie back to the server in the future if the browser does not have an HTTPS connection.

Please note that secure: true is a recommended option. However, it requires an https-enabled website, i.e., HTTPS is necessary for secure cookies. If secure is set, and you access your site over HTTP, the cookie will not be set. If you have your node.js behind a proxy and are using secure: true, you need to set “trust proxy” in express:

var app = express()
app.set('trust proxy', 1) // trust first proxy 
app.use(session({
     secret: 'keyboard cat',
      resave: false,
      saveUninitialized: true,
      cookie: { secure: true }
  }))

For using secure cookies in production, but allowing for testing in development, the following is an example of enabling this setup based on NODE_ENV in express:

var app = express()
var sess = {
      secret: 'keyboard cat',
      cookie: {}
}

if (app.get('env') === 'production') {
      app.set('trust proxy', 1) // trust first proxy 
      sess.cookie.secure = true // serve secure cookies 
}

app.use(session(sess))
The cookie.secure option can also be set to the special value 'auto' to have this setting automatically match the determined security of the connection. Be careful when using this setting if the site is available both as HTTP and HTTPS, as once the cookie is set on HTTPS, it will no longer be visible over HTTP. This is useful when the Express "trust proxy" setting is properly setup to simplify development vs production configuration.

genid

Function to call to generate a new session ID. Provide a function that returns a string that will be used as a session ID. The function is given req as the first argument if you want to use some value attached to req when generating the ID.

The default value is a function which uses the uid-safe library to generate IDs.

NOTE be careful to generate unique IDs so your sessions do not conflict.

app.use(session({
  genid: function(req) {
    return genuuid() // use UUIDs for session IDs 
  },
  secret: 'keyboard cat'
}))
name

The name of the session ID cookie to set in the response (and read from in the request).

The default value is 'connect.sid'.

Note if you have multiple apps running on the same hostname (this is just the name, i.e. localhost or 127.0.0.1; different schemes and ports do not name a different hostname), then you need to separate the session cookies from each other. The simplest method is to simply set different names per app.

proxy

Trust the reverse proxy when setting secure cookies (via the “X-Forwarded-Proto” header).

The default value is undefined.

  • true The “X-Forwarded-Proto” header will be used.
  • false All headers are ignored and the connection is considered secure only if there is a direct TLS/SSL connection.
  • undefined Uses the “trust proxy” setting from express
resave

Forces the session to be saved back to the session store, even if the session was never modified during the request. Depending on your store this may be necessary, but it can also create race conditions where a client makes two parallel requests to your server and changes made to the session in one request may get overwritten when the other request ends, even if it made no changes (this behavior also depends on what store you’re using).

The default value is true, but using the default has been deprecated, as the default will change in the future. Please research into this setting and choose what is appropriate to your use-case. Typically, you’ll want false.

How do I know if this is necessary for my store? The best way to know is to check with your store if it implements the touch method. If it does, then you can safely set resave: false. If it does not implement the touch method and your store sets an expiration date on stored sessions, then you likely need resave: true.

rolling

Force a session identifier cookie to be set on every response. The expiration is reset to the original maxAge, resetting the expiration countdown.

The default value is false.

Note When this option is set to true but the saveUninitialized option is set to false, the cookie will not be set on a response with an uninitialized session.

saveUninitialized

Forces a session that is “uninitialized” to be saved to the store. A session is uninitialized when it is new but not modified. Choosing false is useful for implementing login sessions, reducing server storage usage, or complying with laws that require permission before setting a cookie. Choosing false will also help with race conditions where a client makes multiple parallel requests without a session.

The default value is true, but using the default has been deprecated, as the default will change in the future. Please research into this setting and choose what is appropriate to your use-case.

Note if you are using Session in conjunction with PassportJS, Passport will add an empty Passport object to the session for use after a user is authenticated, which will be treated as a modification to the session, causing it to be saved. This has been fixed in PassportJS 0.3.0

secret
Required option

This is the secret used to sign the session ID cookie. This can be either a string for a single secret, or an array of multiple secrets. If an array of secrets is provided, only the first element will be used to sign the session ID cookie, while all the elements will be considered when verifying the signature in requests.

store

The session store instance, defaults to a new MemoryStore instance.

unset

Control the result of unsetting req.session (through delete, setting to null, etc.).

The default value is 'keep'.

  • 'destroy' The session will be destroyed (deleted) when the response ends.
  • 'keep' The session in the store will be kept, but modifications made during the request are ignored and not saved.
req.session

To store or access session data, simply use the request property req.session, which is (generally) serialized as JSON by the store, so nested objects are typically fine. For example below is a user-specific view counter:

// Use the session middleware 
app.use(session({ secret: 'keyboard cat', cookie:     { maxAge: 60000 }}))

// Access the session as req.session 
app.get('/', function(req, res, next) {
     var sess = req.session
      if (sess.views) {
    sess.views++
    res.setHeader('Content-Type', 'text/html')
    res.write('<p>views: ' + sess.views + '</p>')
    res.write('<p>expires in: ' +     (sess.cookie.maxAge / 1000) + 's</p>')
        res.end()
      } else {
        sess.views = 1
    res.end('welcome to the session demo. refresh!')
      }
})
Session.regenerate(callback)

To regenerate the session simply invoke the method. Once complete, a new SID and Session instance will be initialized at req.session and the callback will be invoked.

req.session.regenerate(function(err) {
      // will have a new session here 
})
Session.destroy(callback)

Destroys the session and will unset the req.session property. Once complete, the callback will be invoked.

req.session.destroy(function(err) {
      // cannot access session here 
})
Session.reload(callback)

Reloads the session data from the store and re-populates the req.session object. Once complete, the callback will be invoked.

req.session.reload(function(err) {
      // session updated 
})
Session.save(callback)

Save the session back to the store, replacing the contents on the store with the contents in memory (though a store may do something else–consult the store’s documentation for exact behavior).

This method is automatically called at the end of the HTTP response if the session data has been altered (though this behavior can be altered with various options in the middleware constructor). Because of this, typically this method does not need to be called.

There are some cases where it is useful to call this method, for example, long- lived requests or in WebSockets.

req.session.save(function(err) {
      // session saved 
})
Session.touch()

Updates the .maxAge property. Typically this is not necessary to call, as the session middleware does this for you.

req.session.id

Each session has a unique ID associated with it. This property will contain the session ID and cannot be modified.

Each session has a unique cookie object accompany it. This allows you to alter the session cookie per visitor. For example we can set req.session.cookie.expires to false to enable the cookie to remain for only the duration of the user-agent.

Alternatively req.session.cookie.maxAge will return the time remaining in milliseconds, which we may also re-assign a new value to adjust the .expires property appropriately. The following are essentially equivalent

var hour = 3600000
req.session.cookie.expires = new Date(Date.now() + hour)
req.session.cookie.maxAge = hour

For example when maxAge is set to 60000 (one minute), and 30 seconds has elapsed it will return 30000 until the current request has completed, at which time req.session.touch() is called to reset req.session.maxAge to its original value.

req.session.cookie.maxAge // => 30000 
req.sessionID

To get the ID of the loaded session, access the request property req.sessionID. This is simply a read-only value set when a session is loaded/created.

Session Store Implementation

Every session store must be an EventEmitter and implement specific methods. The following methods are the list of
required, recommended, and optional.

  • Required methods are ones that this module will always call on the store.
  • Recommended methods are ones that this module will call on the store if available.
  • Optional methods are ones this module does not call at all, but helps present uniform stores to users.
    For an example implementation view the connect-redis repo.
store.all(callback)
Optional

This optional method is used to get all sessions in the store as an array. The callback should be called as callback(error, sessions).

store.destroy(sid, callback)
Required

This required method is used to destroy/delete a session from the store given a session ID (sid). The callback should be called as callback(error) once the session is destroyed.

store.clear(callback)
Optional

This optional method is used to delete all sessions from the store. The callback should be called as callback(error) once the store is cleared.

store.length(callback)
Optional

This optional method is used to get the count of all sessions in the store. The callback should be called as callback(error, len).

store.get(sid, callback)
Required

This required method is used to get a session from the store given a session ID (sid). The callback should be called as callback(error, session).

The session argument should be a session if found, otherwise null or undefined if the session was not found (and there was no error). A special case is made when error.code === 'ENOENT' to act like callback(null, null).

store.set(sid, session, callback)
Required

This required method is used to upsert a session into the store given a session ID (sid) and session (session) object. The callback should be called as callback(error) once the session has been set in the store.

store.touch(sid, session, callback)

This recommended method is used to “touch” a given session given a session ID (sid) and session (session) object. The callback should be called as callback(error) once the session has been touched.

This is primarily used when the store will automatically delete idle sessions and this method is used to signal to the store the given session is active, potentially resetting the idle timer.

Compatible Session Stores

The following modules implement a session store that is compatible with this module. Please make a PR to add additional modules :)

 aerospike-session-store A session store using Aerospike.

cassandra-store An Apache Cassandra-based session store.

cluster-store A wrapper for using in-process / embedded stores - such as SQLite (via knex), leveldb, files, or memory - with node cluster (desirable for Raspberry Pi 2 and other multi-core embedded devices).

connect-azuretables An Azure Table Storage-based session store.

[connect-couchbase] (https://www.npmjs.com/package/connect-couchbase)A couchbase-based session store.

connect-datacache An IBM Bluemix Data Cache-based session store.

connect-db2 An IBM DB2-based session store built using ibm_db module.

connect-dynamodb A DynamoDB-based session store.

connect-loki A Loki.js-based session store.

connect-ml A MarkLogic Server-based session store.

connect-mssql A SQL Server-based session store.

connect-monetdb A MonetDB-based session store.

connect-mongo A MongoDB-based session store.

connect-mongodb-session Lightweight MongoDB-based session store built and maintained by MongoDB.

connect-pg-simple A PostgreSQL-based session store.

connect-redis A Redis-based session store.

connect-memcached A memcached-based session store.

connect-session-knex A session store using Knex.js, which is a SQL query builder for PostgreSQL, MySQL, MariaDB, SQLite3, and Oracle.

connect-session-sequelize A session store using Sequelize.js, which is a Node.js / io.js ORM for PostgreSQL, MySQL, SQLite and MSSQL.

express-mysql-session A session store using native MySQL via the node-mysql module.

express-sessions: A session store supporting both MongoDB and Redis.

connect-sqlite3 A SQLite3session store modeled after the TJ’s connect-redis store.

documentdb-sessionA session store for Microsoft Azure’s DocumentDB NoSQL database service.

express-nedb-session A NeDB-based session store.

express-session-level A LevelDB based session store.

express-etcd An etcd based session store.

hazelcast-store A Hazelcast-based session store built on the Hazelcast Node Client.

[level-session-store] A LevelDB-based session store.

medea-session-store A Medea-based session store.

mssql-session-store A SQL Server-based session store.

nedb-session-store An alternate NeDB-based (either in-memory or file-persisted) session store.

sequelstore-connect A session store using Sequelize.js.

session-file-storeA file system-based session store.

session-rethinkdb* A RethinkDB-based session store.

Example

A simple example using express-session to store page views for a user.

express框架之session 内存存储

1 var express = require('express');
 2 var session = require('express-session');
 3 var cookieParser = require('cookie-parser');
 4 
 5 var app = express();
 6 
7 app.use(cookieParser());
 8 app.use(session({
 9     secret: '12345',
10     name: 'testapp',   //这里的name值得是cookie的name,默认cookie的name是:connect.sid
11     cookie: {maxAge: 80000 },  //设置maxAge是80000ms,即80s后session和相应的cookie失效过期
12     resave: false,
13     saveUninitialized: true,
14 }));
15 
16 
17 app.get('/awesome', function(req, res){
18     
19     if(req.session.lastPage) {
20         console.log('Last page was: ' + req.session.lastPage + ".");    
21     }    
22     req.session.lastPage = '/awesome'; //每一次访问时,session对象的lastPage会自动的保存或更新内存中的session中去。
23     res.send("You're Awesome. And the session expired time is: " + req.session.cookie.maxAge);
24 });
25 
26 app.get('/radical', function(req, res){
27     if (req.session.lastPage) {
28         console.log('Last page was: ' + req.session.lastPage + ".");    
29     }
30     req.session.lastPage = '/radical';  
31     res.send('What a radical visit! And the session expired time is: ' + req.session.cookie.maxAge);
32 });
33 
34 app.get('/tubular', function(req, res){
35     if (req.session.lastPage){
36         console.log("Last page was: " + req.session.lastPage + ".");    
37     }
    38 
39     req.session.lastPage = '/tubular';
40     res.send('Are you a suffer? And the session expired time is: ' + req.session.cookie.maxAge);
41 });
42 
43 
44 app.listen(5000);

express框架之session 数据库存储

 有时候,我们需要session的声明周期要长一点,比如好多网站有个免密码两周内自动登录的功能。基于这个需求,session必须寻找内存之外的存储载体,数据库能提供完美的解决方案。这里,我选用的是mongodb数据库,作为一个NoSQL数据库,它的基础数据对象时database-collection-document 对象模型非常直观并易于理解,针对node.js 也提供了丰富的驱动和API。express框架提供了针对mongodb的中间件:connect-mongo,我们只需在挂载session的时候在options中传入mongodb的参数即可,程序运行的时候, express app 会自动的替我们管理session的存储,更新和删除。具体可以参考:
https://github.com/kcbanner/connect-mongo

 1 var express = require('express');
 2 var session = require('express-session');
 3 var cookieParser = require('cookie-parser');
     4 var MongoStore = require('connect-mongo')(session);
 5 var app = express();
 6 
     7 app.use(cookieParser());
 8 app.use(session({
 9     secret: '12345',
10     name: 'testapp',
11     cookie: {maxAge: 80000 },
12     resave: false,
13     saveUninitialized: true,
14     store: new MongoStore({   //创建新的mongodb数据库
15         host: 'localhost',    //数据库的地址,本机的话就是127.0.0.1,也可以是网络主机
16         port: 27017,          //数据库的端口号
17         db: 'test-app'        //数据库的名称。
18     })
    19 }));
20 
21 
    22 app.get('/awesome', function(req, res){
23     
24     if(req.session.lastPage) {
25         console.log('Last page was: ' + req.session.lastPage + ".");    
26     }    
27     req.session.lastPage = '/awesome';
28     res.send("You're Awesome. And the session expired time is: " + req.session.cookie.maxAge);
29 });
30 
31 app.get('/radical', function(req, res){
32     if (req.session.lastPage) {
33         console.log('Last page was: ' + req.session.lastPage + ".");    
34     }
35     req.session.lastPage = '/radical';
36     res.send('What a radical visit! And the session expired time is: ' + req.session.cookie.maxAge);
37 });
38 
39 app.get('/tubular', function(req, res){
40     if (req.session.lastPage){
41         console.log("Last page was: " + req.session.lastPage + ".");    
42     }
43 
44     req.session.lastPage = '/tubular';
45     res.send('Are you a suffer? And the session expired time is: ' + req.session.cookie.maxAge);
46 });
47 
48 
49 app.listen(5000);

session的生命周期:

  由于session是存在服务器端数据库的,所以的它的生命周期可以持久化,而不仅限于浏览器关闭的时间。具体是由cookie.maxAge 决定:如果maxAge设定是1个小时,那么从这个因浏览器访问服务器导致session创建开始后,session会一直保存在服务器端,即使浏览器关闭,session也会继续存在。如果此时服务器宕机,只要开机后数据库没发生不可逆转的破坏,maxAge时间没过期,那么session是可以继续保持的。

  当maxAge时间过期后,session会自动的数据库中移除,对应的还有浏览器的cookie。不过,由于connect-mongo的特殊机制(每1分钟检查一次过期session),session的移除可能在时间上会有一定的滞后。
部分参考:https://www.npmjs.com/package/express-session#cookiemaxage

javascript一

#javascript

对象


对象使用和属性

JavaScript 中所有变量都可以当作对象使用,除了两个例外 nullundefined

false.toString(); // 'false'
[1, 2, 3].toString(); // '1,2,3'

function Foo(){}
Foo.bar = 1;
Foo.bar; // 1

一个常见的误解是数字的字面值(literal)不能当作对象使用。这是因为 JavaScript 解析器的一个错误, 它试图将点操作符解析为浮点数字面值的一部分。

2.toString(); // 出错:SyntaxError

有很多变通方法可以让数字的字面值看起来像对象。

2..toString(); // 第二个点号可以正常解析
2 .toString(); // 注意点号前面的空格
(2).toString(); // 2先被计算

对象作为数据类型

JavaScript 的对象可以作为哈希表使用,主要用来保存命名的键与值的对应关系。

使用对象的字面语法 - {} - 可以创建一个简单对象。这个新创建的对象从 Object.prototype 继承下面,没有任何自定义属性

var foo = {}; // 一个空对象

// 一个新对象,拥有一个值为12的自定义属性'test'
var bar = {test: 12}; 

访问属性

有两种方式来访问对象的属性,点操作符或者中括号操作符。

var foo = {name: 'kitten'}
foo.name; // kitten
foo['name']; // kitten

var get = 'name';
foo[get]; // kitten

foo.1234; // SyntaxError
foo['1234']; // works

两种语法是等价的,但是中括号操作符在下面两种情况下依然有效

  • 动态设置属性
  • 属性名不是一个有效的变量名(译者注:比如属性名中包含空格,或者属性名是 JS 的关键词)
    译者注:在 JSLint 语法检测工具中,点操作符是推荐做法。

    删除属性

删除属性的唯一方法是使用 delete 操作符;设置属性为 undefined 或者 null 并不能真正的删除属性, 而仅仅是移除了属性和值的关联。

var obj = {
    bar: 1,
    foo: 2,
    baz: 3
};
obj.bar = undefined;
obj.foo = null;
delete obj.baz;

for(var i in obj) {
    if (obj.hasOwnProperty(i)) {
        console.log(i, '' + obj[i]);
    }
}

上面的输出结果有 bar undefinedfoo null - 只有 baz 被真正的删除了,所以从输出结果中消失。

属性名的语法

var test = {
    'case': 'I am a keyword so I must be notated as a string',
    delete: 'I am a keyword too so me' // 出错:SyntaxError
};

对象的属性名可以使用字符串或者普通字符声明。但是由于 JavaScript 解析器的另一个错误设计, 上面的第二种声明方式在 ECMAScript 5 之前会抛出 SyntaxError 的错误。

这个错误的原因是 delete 是 JavaScript 语言的一个关键词;因此为了在更低版本的 JavaScript 引擎下也能正常运行, 必须使用字符串字面值声明方式。


原型

JavaScript 不包含传统的类继承模型,而是使用 prototype 原型模型。

虽然这经常被当作是 JavaScript 的缺点被提及,其实基于原型的继承模型比传统的类继承还要强大。 实现传统的类继承模型是很简单,但是实现 JavaScript 中的原型继承则要困难的多。 (It is for example fairly trivial to build a classic model on top of it, while the other way around is a far more difficult task.)

由于 JavaScript 是唯一一个被广泛使用的基于原型继承的语言,所以理解两种继承模式的差异是需要一定时间的。

第一个不同之处在于 JavaScript 使用原型链的继承方式。

注意: 简单的使用 Bar.prototype = Foo.prototype 将会导致两个对象共享相同的原型。 因此,改变任意一个对象的原型都会影响到另一个对象的原型,在大多数情况下这不是希望的结果。

function Foo() {
    this.value = 42;
}
Foo.prototype = {
        method: function() {}
};

function Bar() {}

// 设置Bar的prototype属性为Foo的实例对象
Bar.prototype = new Foo();
Bar.prototype.foo = 'Hello World';

// 修正Bar.prototype.constructor为Bar本身
Bar.prototype.constructor = Bar;

var test = new Bar() // 创建Bar的一个新实例

// 原型链
test [Bar的实例]
    Bar.prototype [Foo的实例] 
        { foo: 'Hello World' }
        Foo.prototype
            {method: ...};
            Object.prototype
                {toString: ... /* etc. */};

上面的例子中,test 对象从 Bar.prototypeFoo.prototype 继承下来;因此, 它能访问 Foo 的原型方法 method。同时,它也能够访问那个定义在原型上的 Foo 实例属性 value。 需要注意的是 new Bar() 不会创造出一个新的 Foo实例,而是 重复使用它原型上的那个实例;因此,所有的 Bar 实例都会共享相同的 value 属性。

注意: 不要使用 Bar.prototype = Foo,因为这不会执行 Foo 的原型,而是指向函数 Foo。 因此原型链将会回溯到 Function.prototype 而不是Foo.prototype,因此 method 将不会在 Bar 的原型链上。
属性查找

当查找一个对象的属性时,JavaScript 会向上遍历原型链,直到找到给定名称的属性为止。

到查找到达原型链的顶部 - 也就是 Object.prototype - 但是仍然没有找到指定的属性,就会返回 undefined

原型属性

当原型属性用来创建原型链时,可以把任何类型的值赋给它(prototype)。 然而将原子类型赋给 prototype 的操作将会被忽略。

function Foo() {}
Foo.prototype = 1; // 无效

而将对象赋值给 prototype,正如上面的例子所示,将会动态的创建原型链。

性能

如果一个属性在原型链的上端,则对于查找时间将带来不利影响。特别的,试图获取一个不存在的属性将会遍历整个原型链。

并且,当使用 for in 循环遍历对象的属性时,原型链上的所有属性都将被访问。

扩展内置类型的原型

一个错误特性被经常使用,那就是扩展 Object.prototype 或者其他内置类型的原型对象。

这种技术被称之为 monkey patching 并且会破坏封装。虽然它被广泛的应用到一些 JavaScript 类库中比如 Prototype, 但是我仍然不认为为内置类型添加一些非标准的函数是个好主意。

扩展内置类型的唯一理由是为了和新的 JavaScript 保持一致,比如 Array.forEach

译者注:这是编程领域常用的一种方式,称之为 Backport,也就是将新的补丁添加到老版本中。

总结

在写复杂的 JavaScript 应用之前,充分理解原型链继承的工作方式是每个 JavaScript 程序员必修的功课。 要提防原型链过长带来的性能问题,并知道如何通过缩短原型链来提高性能。 更进一步,绝对不要扩展内置类型的原型,除非是为了和新的 JavaScript 引擎兼容。

hasOwnProperty 函数

为了判断一个对象是否包含自定义属性而不是原型链上的属性, 我们需要使用继承自 Object.prototypehasOwnProperty 方法。

注意: 通过判断一个属性是否 undefined是不够的。 因为一个属性可能确实存在,只不过它的值被设置为undefined
hasOwnProperty 是 JavaScript 中唯一一个处理属性但是不查找原型链的函数。

// 修改Object.prototype
Object.prototype.bar = 1; 
var foo = {goo: undefined};

foo.bar; // 1
'bar' in foo; // true

foo.hasOwnProperty('bar'); // false
foo.hasOwnProperty('goo'); // true

只有 hasOwnProperty 可以给出正确和期望的结果,这在遍历对象的属性时会很有用。 没有其它方法可以用来排除原型链上的属性,而不是定义在对象自身上的属性。

hasOwnProperty 作为属性

JavaScript 不会保护 hasOwnProperty 被非法占用,因此如果一个对象碰巧存在这个属性, 就需要使用外部的hasOwnProperty 函数来获取正确的结果。

var foo = {
    hasOwnProperty: function() {
            return false;
    },
    bar: 'Here be dragons'
};

foo.hasOwnProperty('bar'); // 总是返回 false

// 使用其它对象的 hasOwnProperty,并将其上下文设置为foo
({}).hasOwnProperty.call(foo, 'bar'); // true

结论

当检查对象上某个属性是否存在时,hasOwnProperty 是唯一可用的方法。 同时在使用 for in loop 遍历对象时,推荐总是使用 hasOwnProperty 方法, 这将会避免原型对象扩展带来的干扰。

for in 循环

in 操作符一样,for in 循环同样在查找对象属性时遍历原型链上的所有属性。

注意: for in 循环不会遍历那些 enumerable 设置为 false 的属性;比如数组的 length 属性。
// 修改 Object.prototype
Object.prototype.bar = 1;

var foo = {moo: 2};
for(var i in foo) {
    console.log(i); // 输出两个属性:bar 和 moo
}

由于不可能改变 for in 自身的行为,因此有必要过滤出那些不希望出现在循环体中的属性, 这可以通过 Object.prototype 原型上的 hasOwnProperty 函数来完成。

注意: 由于 for in 总是要遍历整个原型链,因此如果一个对象的继承层次太深的话会影响性能。
使用 hasOwnProperty 过滤

// foo 变量是上例中的
for(var i in foo) {
    if (foo.hasOwnProperty(i)) {
        console.log(i);
    }
}

这个版本的代码是唯一正确的写法。由于我们使用了 hasOwnProperty,所以这次只输出moo。 如果不使用 hasOwnProperty,则这段代码在原生对象原型(比如 Object.prototype)被扩展时可能会出错。

一个广泛使用的类库 Prototype 就扩展了原生的 JavaScript 对象。 因此,当这个类库被包含在页面中时,不使用 hasOwnProperty 过滤的 for in 循环难免会出问题。

总结

推荐总是使用 hasOwnProperty。不要对代码运行的环境做任何假设,不要假设原生对象是否已经被扩展了。

函数


函数声明与表达式

函数是JavaScript中的一等对象,这意味着可以把函数像其它值一样传递。 一个常见的用法是把匿名函数作为回调函数传递到异步函数中。

函数声明

function foo() {}

上面的方法会在执行前被 解析(hoisted),因此它存在于当前上下文的任意一个地方, 即使在函数定义体的上面被调用也是对的。

foo(); // 正常运行,因为foo在代码运行前已经被创建
function foo() {}

函数赋值表达式

var foo = function() {};
这个例子把一个匿名的函数赋值给变量 foo。

foo; // 'undefined'
foo(); // 出错:TypeError
var foo = function() {};

由于 var 定义了一个声明语句,对变量 foo 的解析是在代码运行之前,因此foo 变量在代码运行时已经被定义过了。

但是由于赋值语句只在运行时执行,因此在相应代码执行之前, foo 的值缺省为 undefined

命名函数的赋值表达式

另外一个特殊的情况是将命名函数赋值给一个变量。

var foo = function bar() {
    bar(); // 正常运行
}
bar(); // 出错:ReferenceError

bar 函数声明外是不可见的,这是因为我们已经把函数赋值给了 foo; 然而在 bar 内部依然可见。这是由于 JavaScript 的 命名处理 所致, 函数名在函数内总是可见的。

注意:在IE8及IE8以下版本浏览器bar在外部也是可见的,是因为浏览器对命名函数赋值表达式进行了错误的解析, 解析成两个函数 foobar


this 的工作原理

JavaScript 有一套完全不同于其它语言的对 this 的处理机制。 在五种不同的情况下 ,this 指向的各不相同。

全局范围内

this;

当在全部范围内使用 this,它将会指向全局对象。

译者注:浏览器中运行的 JavaScript 脚本,这个全局对象是 window

函数调用

foo();

这里 this 也会指向全局对象。

ES5 注意: 在严格模式下(strict mode),不存在全局变量。 这种情况下 this 将会是 undefined

方法调用

test.foo(); 

这个例子中,this 指向 test 对象。

调用构造函数

new foo(); 

如果函数倾向于和 new 关键词一块使用,则我们称这个函数是 构造函数。 在函数内部,this 指向新创建的对象。

显式的设置 this

function foo(a, b, c) {}

var bar = {};
foo.apply(bar, [1, 2, 3]); // 数组将会被扩展,如下所示
foo.call(bar, 1, 2, 3); // 传递到foo的参数是:a = 1, b = 2, c = 3

当使用 Function.prototype 上的 call 或者 apply 方法时,函数内的 this 将会被 显式设置为函数调用的第一个参数。

因此函数调用的规则在上例中已经不适用了,在foo 函数内 this 被设置成了 bar

注意: 在对象的字面声明语法中,this 不能用来指向对象本身。 因此 var obj = {me: this} 中的 me 不会指向 obj,因为 this 只可能出现在上述的五种情况中。 译者注:这个例子中,如果是在浏览器中运行,obj.me 等于 window 对象。
常见误解

尽管大部分的情况都说的过去,不过第一个规则(译者注:这里指的应该是第二个规则,也就是直接调用函数时,this 指向全局对象) 被认为是JavaScript语言另一个错误设计的地方,因为它从来就没有实际的用途。

Foo.method = function() {
    function test() {
        // this 将会被设置为全局对象(译者注:浏览器环境中也就是 window 对象)
    }
    test();
}

一个常见的误解是 test 中的 this 将会指向 Foo 对象,实际上不是这样子的。

为了在 test 中获取对 Foo 对象的引用,我们需要在 method 函数内部创建一个局部变量指向 Foo 对象。

Foo.method = function() {
    var that = this;
    function test() {
            // 使用 that 来指向 Foo 对象
    }
    test();
}

that 只是我们随意起的名字,不过这个名字被广泛的用来指向外部的 this 对象。 在 闭包 一节,我们可以看到 that 可以作为参数传递。

方法的赋值表达式

另一个看起来奇怪的地方是函数别名,也就是将一个方法赋值给一个变量。

var test = someObject.methodTest;
test();

上例中,test 就像一个普通的函数被调用;因此,函数内的 this 将不再被指向到 someObject 对象。

虽然 this 的晚绑定特性似乎并不友好,但这确实是基于原型继承赖以生存的土壤。

function Foo() {}
Foo.prototype.method = function() {};

function Bar() {}
Bar.prototype = Foo.prototype;

new Bar().method();

method 被调用时,this 将会指向 Bar 的实例对象。

闭包和引用

闭包是 JavaScript 一个非常重要的特性,这意味着当前作用域总是能够访问外部作用域中的变量。 因为 函数 是 JavaScript 中唯一拥有自身作用域的结构,因此闭包的创建依赖于函数。

模拟私有变量

function Counter(start) {
    var count = start;
    return {
        increment: function() {
            count++;
        },

        get: function() {
            return count;
        }
        }
}

var foo = Counter(4);
foo.increment();
foo.get(); // 5

这里,Counter 函数返回两个闭包,函数 increment和函数 get。 这两个函数都维持着 对外部作用域 Counter 的引用,因此总可以访问此作用域内定义的变量 count.

为什么不可以在外部访问私有变量

因为 JavaScript 中不可以对作用域进行引用或赋值,因此没有办法在外部访问 count 变量。 唯一的途径就是通过那两个闭包。

var foo = new Counter(4);
foo.hack = function() {
    count = 1337;
};

上面的代码不会改变定义在 Counter 作用域中的 count 变量的值,因为 foo.hack 没有 定义在那个作用域内。它将会创建或者覆盖全局变量 count

循环中的闭包

一个常见的错误出现在循环中使用闭包,假设我们需要在每次循环中调用循环序号

for(var i = 0; i < 10; i++) {
    setTimeout(function() {
            console.log(i);  
    }, 1000);
}

上面的代码不会输出数字 09,而是会输出数字 10 十次。

console.log被调用的时候,匿名函数保持对外部变量 i 的引用,此时for循环已经结束, i 的值被修改成了 10.

为了得到想要的结果,需要在每次循环中创建变量 i 的拷贝。

避免引用错误

为了正确的获得循环序号,最好使用 匿名包装器(译者注:其实就是我们通常说的自执行匿名函数)。

for(var i = 0; i < 10; i++) {
    (function(e) {
        setTimeout(function() {
            console.log(e);  
        }, 1000);
    })(i);
}

外部的匿名函数会立即执行,并把 i 作为它的参数,此时函数内 e变量就拥有了 i 的一个拷贝。

当传递给 setTimeout 的匿名函数执行时,它就拥有了对 e 的引用,而这个值是不会被循环改变的。

有另一个方法完成同样的工作,那就是从匿名包装器中返回一个函数。这和上面的代码效果一样。

for(var i = 0; i < 10; i++) {
    setTimeout((function(e) {
        return function() {
            console.log(e);
        }
    })(i), 1000)
}

arguments 对象

JavaScript 中每个函数内都能访问一个特别变量 arguments。这个变量维护着所有传递到这个函数中的参数列表。

注意: 由于 arguments 已经被定义为函数内的一个变量。 因此通过 var 关键字定义 arguments 或者将 arguments 声明为一个形式参数, 都将导致原生的 arguments 不会被创建。
arguments 变量不是一个数组(Array)。 尽管在语法上它有数组相关的属性 length,但它不从 Array.prototype 继承,实际上它是一个对象(Object)。

因此,无法对 arguments 变量使用标准的数组方法,比如 push, pop 或者 slice。 虽然使用 for 循环遍历也是可以的,但是为了更好的使用数组方法,最好把它转化为一个真正的数组。

转化为数组

下面的代码将会创建一个新的数组,包含所有 arguments 对象中的元素。

Array.prototype.slice.call(arguments);

这个转化比较慢,在性能不好的代码中不推荐这种做法。

传递参数

下面是将参数从一个函数传递到另一个函数的推荐做法。

function foo() {
    bar.apply(null, arguments);
}
function bar(a, b, c) {
    // 干活
}

另一个技巧是同时使用 callapply,创建一个快速的解绑定包装器。

function Foo() {}

Foo.prototype.method = function(a, b, c) {
    console.log(this, a, b, c);
};

// 创建一个解绑定的 "method"
// 输入参数为: this, arg1, arg2...argN
Foo.method = function() {

    // 结果: Foo.prototype.method.call(this, arg1, arg2... argN)
    Function.call.apply(Foo.prototype.method, arguments);
};
译者注:上面的 Foo.method 函数和下面代码的效果是一样的:

Foo.method = function() {
var args = Array.prototype.slice.call(arguments);
Foo.prototype.method.apply(args[0], args.slice(1));
};

自动更新

arguments 对象为其内部属性以及函数形式参数创建 gettersetter 方法。

因此,改变形参的值会影响到 arguments 对象的值,反之亦然。

function foo(a, b, c) {
    arguments[0] = 2;
    a; // 2                                                           

    b = 4;
    arguments[1]; // 4

    var d = c;
    d = 9;
    c; // 3
}
foo(1, 2, 3);

性能真相

不管它是否有被使用,arguments 对象总会被创建,除了两个特殊情况 - 作为局部变量声明和作为形式参数。

argumentsgetterssetters 方法总会被创建;因此使用 arguments 对性能不会有什么影响。 除非是需要对 arguments 对象的属性进行多次访问。

ES5 提示: 这些 getterssetters 在严格模式下(strict mode)不会被创建。
译者注:在 MDC 中对 strict mode 模式下 arguments 的描述有助于我们的理解,请看下面代码:

// 阐述在 ES5 的严格模式下 arguments 的特性
function f(a) {
      "use strict";
      a = 42;
      return [a, arguments[0]];
    }
var pair = f(17);
console.assert(pair[0] === 42);
console.assert(pair[1] === 17);

然而,的确有一种情况会显著的影响现代 JavaScript 引擎的性能。这就是使用 arguments.callee。

function foo() {
    arguments.callee; // do something with this function object
    arguments.callee.caller; // and the calling function object
}

function bigLoop() {
    for(var i = 0; i < 100000; i++) {
        foo(); // Would normally be inlined...
    }
}

上面代码中,foo 不再是一个单纯的内联函数 inlining(译者注:这里指的是解析器可以做内联处理), 因为它需要知道它自己和它的调用者。 这不仅抵消了内联函数带来的性能提升,而且破坏了封装,因此现在函数可能要依赖于特定的上下文。

因此强烈建议大家不要使用 arguments.callee 和它的属性。

ES5 提示: 在严格模式下,arguments.callee 会报错 TypeError,因为它已经被废除了。

构造函数

JavaScript 中的构造函数和其它语言中的构造函数是不同的。 通过 new 关键字方式调用的函数都被认为是构造函数。

在构造函数内部 - 也就是被调用的函数内 - this 指向新创建的对象 Object。 这个新创建的对象的 prototype 被指向到构造函数的 prototype。

如果被调用的函数没有显式的 return 表达式,则隐式的会返回 this 对象 - 也就是新创建的对象。

function Foo() {
    this.bla = 1;
}

Foo.prototype.test = function() {
    console.log(this.bla);
};

var test = new Foo();

上面代码把 Foo 作为构造函数调用,并设置新创建对象的 prototype 为 Foo.prototype。

显式的 return 表达式将会影响返回结果,但仅限于返回的是一个对象。

function Bar() {
    return 2;
    }
new Bar(); // 返回新创建的对象

function Test() {
    this.value = 2;

return {
    foo: 1
};
}
new Test(); // 返回的对象

译者注:new Bar() 返回的是新创建的对象,而不是数字的字面值 2。 因此 new Bar().constructor === Bar,但是如果返回的是数字对象,结果就不同了,如下所示

function Bar() {
    return new Number(2);
}
new Bar().constructor === Number

译者注:这里得到的 new Test()是函数返回的对象,而不是通过new关键字新创建的对象,因此:

(new Test()).value === undefined
(new Test()).foo === 1

如果 new 被遗漏了,则函数不会返回新创建的对象。

function Foo() {
    this.bla = 1; // 获取设置全局参数
}
Foo(); // undefined

虽然上例在有些情况下也能正常运行,但是由于 JavaScript 中 this 的工作原理, 这里的 this 指向全局对象。

工厂模式

为了不使用 new 关键字,构造函数必须显式的返回一个值。

function Bar() {
    var value = 1;
    return {
        method: function() {
            return value;
        }
    }
}
Bar.prototype = {
    foo: function() {}
};

new Bar();
Bar();

上面两种对 Bar 函数的调用返回的值完全相同,一个新创建的拥有 method 属性的对象被返回, 其实这里创建了一个闭包。

还需要注意, new Bar() 并不会改变返回对象的原型(译者注:也就是返回对象的原型不会指向 Bar.prototype)。 因为构造函数的原型会被指向到刚刚创建的新对象,而这里的 Bar 没有把这个新对象返回(译者注:而是返回了一个包含 method 属性的自定义对象)。

在上面的例子中,使用或者不使用 new 关键字没有功能性的区别。

译者注:上面两种方式创建的对象不能访问 Bar 原型链上的属性,如下所示:

var bar1 = new Bar();
typeof(bar1.method); // "function"
typeof(bar1.foo); // "undefined"

var bar2 = Bar();
typeof(bar2.method); // "function"
typeof(bar2.foo); // "undefined"

通过工厂模式创建新对象

我们常听到的一条忠告是不要使用 new 关键字来调用函数,因为如果忘记使用它就会导致错误。

为了创建新对象,我们可以创建一个工厂方法,并且在方法内构造一个新对象。

function Foo() {
var obj = {};
obj.value = 'blub';

var private = 2;
obj.someMethod = function(value) {
    this.value = value;
}

obj.getPrivate = function() {
    return private;
}
return obj;
}

虽然上面的方式比起 new 的调用方式不容易出错,并且可以充分利用私有变量带来的便利, 但是随之而来的是一些不好的地方。

会占用更多的内存,因为新创建的对象不能共享原型上的方法。
为了实现继承,工厂方法需要从另外一个对象拷贝所有属性,或者把一个对象作为新创建对象的原型。
放弃原型链仅仅是因为防止遗漏 new 带来的问题,这似乎和语言本身的思想相违背。
总结

虽然遗漏 new 关键字可能会导致问题,但这并不是放弃使用原型链的借口。 最终使用哪种方式取决于应用程序的需求,选择一种代码书写风格并坚持下去才是最重要的。


作用域与命名空间

尽管 JavaScript 支持一对花括号创建的代码段,但是并不支持块级作用域; 而仅仅支持 函数作用域。

function test() { // 一个作用域
    for(var i = 0; i < 10; i++) { // 不是一个作用域
        // count
    }
    console.log(i); // 10
}

注意: 如果不是在赋值语句中,而是在 return 表达式或者函数参数中,{…} 将会作为代码段解析, 而不是作为对象的字面语法解析。如果考虑到 自动分号插入,这可能会导致一些不易察觉的错误。
译者注:如果 return 对象的左括号和 return 不在一行上就会出错。

// 译者注:下面输出 undefined
function add(a, b) {
    return 
        a + b;
}
console.log(add(1, 2));

JavaScript 中没有显式的命名空间定义,这就意味着所有对象都定义在一个全局共享的命名空间下面。

每次引用一个变量,JavaScript 会向上遍历整个作用域直到找到这个变量为止。 如果到达全局作用域但是这个变量仍未找到,则会抛出 ReferenceError 异常。

隐式的全局变量

// 脚本 A
foo = '42';

// 脚本 B
var foo = '42'

上面两段脚本效果不同。脚本 A 在全局作用域内定义了变量 foo,而脚本 B 在当前作用域内定义变量 foo。

再次强调,上面的效果完全不同,不使用 var 声明变量将会导致隐式的全局变量产生。

// 全局作用域
var foo = 42;
function test() {
    // 局部作用域
    foo = 21;
}
test();
foo; // 21

在函数 test 内不使用 var 关键字声明 foo 变量将会覆盖外部的同名变量。 起初这看起来并不是大问题,但是当有成千上万行代码时,不使用 var 声明变量将会带来难以跟踪的 BUG。

// 全局作用域
var items = [/* 数组 */];
for(var i = 0; i < 10; i++) {
    subLoop();
}

function subLoop() {
        // subLoop 函数作用域
    for(i = 0; i < 10; i++) { // 没有使用 var 声明变量
        // 干活
    }
}

外部循环在第一次调用 subLoop 之后就会终止,因为 subLoop 覆盖了全局变量 i。 在第二个 for 循环中使用 var 声明变量可以避免这种错误。 声明变量时绝对不要遗漏 var 关键字,除非这就是期望的影响外部作用域的行为。

局部变量

JavaScript 中局部变量只可能通过两种方式声明,一个是作为函数参数,另一个是通过 var 关键字声明。

// 全局变量
var foo = 1;
var bar = 2;
var i = 2;

function test(i) {
    // 函数 test 内的局部作用域
    i = 5;

    var foo = 3;
    bar = 4;
}
test(10);

fooi 是函数 test 内的局部变量,而对 bar 的赋值将会覆盖全局作用域内的同名变量。

变量声明提升(Hoisting)

JavaScript 会提升变量声明。这意味着 var 表达式和 function 声明都将会被提升到当前作用域的顶部。

bar();
var bar = function() {};
var someValue = 42;

test();
function test(data) {
if (false) {
    goo = 1;

} else {
    var goo = 2;
}
for(var i = 0; i < 100; i++) {
    var e = data[i];
}
}

上面代码在运行之前将会被转化。JavaScript 将会把 var 表达式和 function 声明提升到当前作用域的顶部。

// var 表达式被移动到这里
var bar, someValue; // 缺省值是 'undefined'

// 函数声明也会提升
function test(data) {
var goo, i, e; // 没有块级作用域,这些变量被移动到函数顶部
if (false) {
    goo = 1;

} else {
    goo = 2;
}
for(i = 0; i < 100; i++) {
    e = data[i];
}
}

bar(); // 出错:TypeError,因为 bar 依然是 'undefined'
someValue = 42; // 赋值语句不会被提升规则(hoisting)影响
bar = function() {};

test();

没有块级作用域不仅导致 var 表达式被从循环内移到外部,而且使一些 if 表达式更难看懂。

在原来代码中,if 表达式看起来修改了全局变量 goo,实际上在提升规则被应用后,却是在修改局部变量。

如果没有提升规则(hoisting)的知识,下面的代码看起来会抛出异常 ReferenceError。

// 检查 SomeImportantThing 是否已经被初始化
if (!SomeImportantThing) {
        var SomeImportantThing = {};
}

实际上,上面的代码正常运行,因为 var 表达式会被提升到全局作用域的顶部。

var SomeImportantThing;

// 其它一些代码,可能会初始化 SomeImportantThing,也可能不会

// 检查是否已经被初始化
if (!SomeImportantThing) {
    SomeImportantThing = {};

}
译者注:在 Nettuts+ 网站有一篇介绍 hoisting 的文章,其中的代码很有启发性。

// 译者注:来自 Nettuts+ 的一段代码,生动的阐述了 JavaScript 中变量声明提升规则

    var myvar = 'my value';  

(function() {  
alert(myvar); // undefined  
var myvar = 'local value';  
})();  

名称解析顺序

JavaScript 中的所有作用域,包括全局作用域,都有一个特别的名称 this 指向当前对象。

函数作用域内也有默认的变量 arguments,其中包含了传递到函数中的参数。

比如,当访问函数内的 foo 变量时,JavaScript 会按照下面顺序查找:

  1. 当前作用域内是否有 var foo 的定义。
  2. 函数形式参数是否有使用 foo 名称的。
  3. 函数自身是否叫做 foo。
  4. 回溯到上一级作用域,然后从 #1 重新开始。
    注意: 自定义 arguments 参数将会阻止原生的 arguments 对象的创建。

    命名空间

只有一个全局作用域导致的常见错误是命名冲突。在 JavaScript中,这可以通过 匿名包装器 轻松解决。

(function() {
// 函数创建一个命名空间

window.foo = function() {
    // 对外公开的函数,创建了闭包
};

})(); // 立即执行此匿名函数

匿名函数被认为是 表达式;因此为了可调用性,它们首先会被执行。

( // 小括号内的函数首先被执行
function() {}
) // 并且返回函数对象
() // 调用上面的执行结果,也就是函数对象

有一些其他的调用函数表达式的方法,比如下面的两种方式语法不同,但是效果一模一样。

// 另外两种方式
+function(){}();
(function(){}());

结论

推荐使用匿名包装器(译者注:也就是自执行的匿名函数)来创建命名空间。这样不仅可以防止命名冲突, 而且有利于程序的模块化。

另外,使用全局变量被认为是不好的习惯。这样的代码容易产生错误并且维护成本较高。

数组


数组遍历与属性

虽然在 JavaScript 中数组是对象,但是没有好的理由去使用 for in 循环 遍历数组。 相反,有一些好的理由不去使用 for in 遍历数组。

注意: JavaScript 中数组不是 关联数组。 JavaScript 中只有对象 来管理键值的对应关系。但是关联数组是保持顺序的,而对象不是。
由于 for in 循环会枚举原型链上的所有属性,唯一过滤这些属性的方式是使用 hasOwnProperty 函数, 因此会比普通的 for 循环慢上好多倍。

遍历

为了达到遍历数组的最佳性能,推荐使用经典的 for 循环。

var list = [1, 2, 3, 4, 5, ...... 100000000];
for(var i = 0, l = list.length; i < l; i++) {
    console.log(list[i]);
}

上面代码有一个处理,就是通过 l = list.length 来缓存数组的长度。

虽然 length 是数组的一个属性,但是在每次循环中访问它还是有性能开销。 可能最新的 JavaScript 引擎在这点上做了优化,但是我们没法保证自己的代码是否运行在这些最近的引擎之上。

实际上,不使用缓存数组长度的方式比缓存版本要慢很多。

length 属性

length 属性的 getter 方式会简单的返回数组的长度,而 setter 方式会截断数组。

var foo = [1, 2, 3, 4, 5, 6];
foo.length = 3;
foo; // [1, 2, 3]

foo.length = 6;
foo; // [1, 2, 3]

译者注: 在 Firebug 中查看此时 foo 的值是: [1, 2, 3, undefined, undefined, undefined] 但是这个结果并不准确,如果你在 Chrome 的控制台查看 foo 的结果,你会发现是这样的: [1, 2, 3] 因为在 JavaScript 中 undefined 是一个变量,注意是变量不是关键字,因此上面两个结果的意义是完全不相同的。

// 译者注:为了验证,我们来执行下面代码,看序号 5 是否存在于 foo 中。
5 in foo; // 不管在 Firebug 或者 Chrome 都返回 false
foo[5] = undefined;
5 in foo; // 不管在 Firebug 或者 Chrome 都返回 true
为 length 设置一个更小的值会截断数组,但是增大 length 属性值不会对数组产生影响。

结论

为了更好的性能,推荐使用普通的 for 循环并缓存数组的 length 属性。 使用 for in 遍历数组被认为是不好的代码习惯并倾向于产生错误和导致性能问题。

Array 构造函数

由于 Array 的构造函数在如何处理参数时有点模棱两可,因此总是推荐使用数组的字面语法 - [] - 来创建数组。

[1, 2, 3]; // 结果: [1, 2, 3]
new Array(1, 2, 3); // 结果: [1, 2, 3]

[3]; // 结果: [3]
new Array(3); // 结果: [] 
new Array('3') // 结果: ['3']

// 译者注:因此下面的代码将会使人很迷惑
new Array(3, 4, 5); // 结果: [3, 4, 5] 
new Array(3) // 结果: [],此数组长度为 3

译者注:这里的模棱两可指的是数组的两种构造函数语法
由于只有一个参数传递到构造函数中(译者注:指的是 new Array(3); 这种调用方式),并且这个参数是数字,构造函数会返回一个 length 属性被设置为此参数的空数组。 需要特别注意的是,此时只有 length 属性被设置,真正的数组并没有生成。

译者注:在 Firebug 中,你会看到 [undefined, undefined, undefined],这其实是不对的。在上一节有详细的分析。

var arr = new Array(3);
arr[1]; // undefined
1 in arr; // false, 数组还没有生成

这种优先于设置数组长度属性的做法只在少数几种情况下有用,比如需要循环字符串,可以避免 for 循环的麻烦。

new Array(count + 1).join(stringToRepeat);

译者注: new Array(3).join(‘#’) 将会返回 ##

结论

应该尽量避免使用数组构造函数创建新数组。推荐使用数组的字面语法。它们更加短小和简洁,因此增加了代码的可读性。

类型

相等与比较

JavaScript 有两种方式判断两个值是否相等。

等于操作符

等于操作符由两个等号组成:==

JavaScript 是弱类型语言,这就意味着,等于操作符会为了比较两个值而进行强制类型转换。

""           ==   "0"           // false
0            ==   ""            // true
0            ==   "0"           // true
false        ==   "false"       // false
false        ==   "0"           // true
false        ==   undefined     // false
false        ==   null          // false
null         ==   undefined     // true
" \t\r\n"    ==   0             // true

上面的表格展示了强制类型转换,这也是使用 == 被广泛认为是不好编程习惯的主要原因, 由于它的复杂转换规则,会导致难以跟踪的问题。

此外,强制类型转换也会带来性能消耗,比如一个字符串为了和一个数字进行比较,必须事先被强制转换为数字。

严格等于操作符

严格等于操作符由三个等号组成:===

不像普通的等于操作符,严格等于操作符不会进行强制类型转换。

""           ===   "0"           // false
0            ===   ""            // false
0            ===   "0"           // false
false        ===   "false"       // false
false        ===   "0"           // false
false        ===   undefined     // false
false        ===   null          // false
null         ===   undefined     // false
" \t\r\n"    ===   0             // false

上面的结果更加清晰并有利于代码的分析。如果两个操作数类型不同就肯定不相等也有助于性能的提升。

比较对象

虽然 == 和 === 操作符都是等于操作符,但是当其中有一个操作数为对象时,行为就不同了。

{} === {};                   // false
new String('foo') === 'foo'; // false
new Number(10) === 10;       // false
var foo = {};
foo === foo;                 // true

这里等于操作符比较的不是值是否相等,而是是否属于同一个身份;也就是说,只有对象的同一个实例才被认为是相等的。 这有点像 Python 中的 is 和 C 中的指针比较。

注意:为了更直观的看到==和===的区别,可以参见JavaScript Equality Table
结论

强烈推荐使用严格等于操作符。如果类型需要转换,应该在比较之前显式的转换, 而不是使用语言本身复杂的强制转换规则。


typeof 操作符

typeof 操作符(和 instanceof 一起)或许是 JavaScript 中最大的设计缺陷, 因为几乎不可能从它们那里得到想要的结果。

尽管 instanceof 还有一些极少数的应用场景,typeof 只有一个实际的应用(译者注:这个实际应用是用来检测一个对象是否已经定义或者是否已经赋值), 而这个应用却不是用来检查对象的类型。

注意: 由于 typeof 也可以像函数的语法被调用,比如 typeof(obj),但这并不是一个函数调用。 那两个小括号只是用来计算一个表达式的值,这个返回值会作为 typeof 操作符的一个操作数。 实际上不存在名为 typeof 的函数。
JavaScript 类型表格

Value               Class      Type

"foo"               String     string
new String("foo")   String     object
1.2                 Number     number
new Number(1.2)     Number     object
true                Boolean    boolean
new Boolean(true)   Boolean    object
new Date()          Date       object
new Error()         Error      object
[1,2,3]             Array      object
new Array(1, 2, 3)  Array      object
new Function("")    Function   function
/abc/g              RegExp     object (function in Nitro/V8)
new RegExp("meow")  RegExp     object (function in Nitro/V8)
{}                  Object     object
new Object()        Object     object

上面表格中,Type 一列表示 typeof 操作符的运算结果。可以看到,这个值在大多数情况下都返回 “object”。

Class 一列表示对象的内部属性 [[Class]] 的值。

JavaScript 标准文档中定义: [[Class]] 的值只可能是下面字符串中的一个: Arguments, Array, Boolean, Date, Error, Function, JSON, Math, Number, Object, RegExp, String.
为了获取对象的 [[Class]],我们需要使用定义在 Object.prototype 上的方法 toString。

对象的类定义

JavaScript 标准文档只给出了一种获取 [[Class]] 值的方法,那就是使用 Object.prototype.toString。

function is(type, obj) {
var clas = Object.prototype.toString.call(obj).slice(8, -1);
return obj !== undefined && obj !== null && clas ===     type;
}

is('String', 'test'); // true
is('String', new String('test')); // true

上面例子中,Object.prototype.toString 方法被调用,this 被设置为了需要获取 [[Class]] 值的对象。

译者注:Object.prototype.toString 返回一种标准格式字符串,所以上例可以通过 slice 截取指定位置的字符串,如下所示:

Object.prototype.toString.call([])    // "[object Array]"
Object.prototype.toString.call({})    // "[object Object]"
Object.prototype.toString.call(2)    // "[object Number]"

ES5 提示: 在 ECMAScript 5 中,为了方便,对 null 和 undefined 调用 Object.prototype.toString 方法, 其返回值由 Object 变成了 Null 和 Undefined。
译者注:这种变化可以从 IE8 和 Firefox 4 中看出区别,如下所示:

// IE8
Object.prototype.toString.call(null)    // "[object Object]"
Object.prototype.toString.call(undefined)    //     "[object Object]"

// Firefox 4
Object.prototype.toString.call(null)    // "[object Null]"
Object.prototype.toString.call(undefined)    // "[object Undefined]"

测试为定义变量

typeof foo !== 'undefined'

上面代码会检测 foo 是否已经定义;如果没有定义而直接使用会导致 ReferenceError 的异常。 这是 typeof 唯一有用的地方。

结论

为了检测一个对象的类型,强烈推荐使用 Object.prototype.toString 方法; 因为这是唯一一个可依赖的方式。正如上面表格所示,typeof 的一些返回值在标准文档中并未定义, 因此不同的引擎实现可能不同。

除非为了检测一个变量是否已经定义,我们应尽量避免使用 typeof 操作符。

instanceof 操作符

instanceof 操作符用来比较两个操作数的构造函数。只有在比较自定义的对象时才有意义。 如果用来比较内置类型,将会和 typeof 操作符 一样用处不大。

比较自定义对象

function Foo() {}
function Bar() {}
Bar.prototype = new Foo();

new Bar() instanceof Bar; // true
new Bar() instanceof Foo; // true

// 如果仅仅设置 Bar.prototype 为函数 Foo 本身,而不是 Foo 构造函数的一个实例
Bar.prototype = Foo;
new Bar() instanceof Foo; // false

instanceof 比较内置类型

new String('foo') instanceof String; // true
new String('foo') instanceof Object; // true

'foo' instanceof String; // false
'foo' instanceof Object; // false

有一点需要注意,instanceof 用来比较属于不同 JavaScript 上下文的对象(比如,浏览器中不同的文档结构)时将会出错, 因为它们的构造函数不会是同一个对象。

结论

instanceof 操作符应该仅仅用来比较来自同一个 JavaScript 上下文的自定义对象。 正如 typeof 操作符一样,任何其它的用法都应该是避免的。


类型转换

JavaScript 是弱类型语言,所以会在任何可能的情况下应用强制类型转换。

// 下面的比较结果是:true
new Number(10) == 10; // Number.toString() 返回的字符串被再次转换为数字

10 == '10';           // 字符串被转换为数字
10 == '+10 ';         // 同上
10 == '010';          // 同上 
isNaN(null) == false; // null 被转换为数字 0
                  // 0 当然不是一个 NaN(译者注:否定之否定)

// 下面的比较结果是:false
10 == 010;
10 == '-10';

ES5 提示: 以 0 开头的数字字面值会被作为八进制数字解析。 而在 ECMAScript 5 严格模式下,这个特性被移除了。
为了避免上面复杂的强制类型转换,强烈推荐使用严格的等于操作符。 虽然这可以避免大部分的问题,但 JavaScript 的弱类型系统仍然会导致一些其它问题。

内置类型的构造函数

内置类型(比如 Number 和 String)的构造函数在被调用时,使用或者不使用 new 的结果完全不同。

new Number(10) === 10;     // False, 对象与数字的比较
Number(10) === 10;         // True, 数字与数字的比较
new Number(10) + 0 === 10; // True, 由于隐式的类型转换

使用内置类型 Number 作为构造函数将会创建一个新的 Number 对象, 而在不使用 new 关键字的 Number 函数更像是一个数字转换器。

另外,在比较中引入对象的字面值将会导致更加复杂的强制类型转换。

最好的选择是把要比较的值显式的转换为三种可能的类型之一。

转换为字符串

'' + 10 === '10'; // true

将一个值加上空字符串可以轻松转换为字符串类型。

转换为数字

+'10' === 10; // true

使用一元的加号操作符,可以把字符串转换为数字。

译者注:字符串转换为数字的常用方法:

+'010' === 10
Number('010') === 10
parseInt('010', 10) === 10  // 用来转换为整数

+'010.2' === 10.2
Number('010.2') === 10.2
parseInt('010.2', 10) === 10

转换为布尔型

通过使用 否 操作符两次,可以把一个值转换为布尔型。

!!'foo';   // true
!!'';      // false
!!'0';     // true
!!'1';     // true
!!'-1'     // true
!!{};      // true
!!true;    // true

核心


为什么不要使用 eval

eval 函数会在当前作用域中执行一段 JavaScript 代码字符串。

var foo = 1;
function test() {
    var foo = 2;
        eval('foo = 3');
    return foo;
}
test(); // 3
foo; // 1

但是 eval 只在被直接调用并且调用函数就是 eval 本身时,才在当前作用域中执行。

var foo = 1;
function test() {
    var foo = 2;
    var bar = eval;
    bar('foo = 3');
    return foo;
}
    test(); // 2
    foo; // 3

译者注:上面的代码等价于在全局作用域中调用 eval,和下面两种写法效果一样:

// 写法一:直接调用全局作用域下的 foo 变量
var foo = 1;
function test() {
    var foo = 2;
    window.foo = 3;
    return foo;
}
test(); // 2
foo; // 3

// 写法二:使用 call 函数修改 eval 执行的上下文为全局作用域
var foo = 1;
function test() {
    var foo = 2;
        eval.call(window, 'foo = 3');
    return foo;
    }
test(); // 2
foo; // 3

在任何情况下我们都应该避免使用 eval 函数。99.9% 使用 eval 的场景都有不使用 eval 的解决方案。

伪装的 eval

定时函数 setTimeout 和 setInterval 都可以接受字符串作为它们的第一个参数。 这个字符串总是在全局作用域中执行,因此 eval 在这种情况下没有被直接调用。

安全问题

eval 也存在安全问题,因为它会执行任意传给它的代码, 在代码字符串未知或者是来自一个不信任的源时,绝对不要使用 eval 函数。

结论

绝对不要使用 eval,任何使用它的代码都会在它的工作方式,性能和安全性方面受到质疑。 如果一些情况必须使用到 eval 才能正常工作,首先它的设计会受到质疑,这不应该是首选的解决方案, 一个更好的不使用 eval 的解决方案应该得到充分考虑并优先采用。

undefined 和 null

JavaScript 有两个表示‘空’的值,其中比较有用的是 undefined。

undefined 的值

undefined 是一个值为 undefined 的类型。

这个语言也定义了一个全局变量,它的值是 undefined,这个变量也被称为 undefined。 但是这个变量不是一个常量,也不是一个关键字。这意味着它的值可以轻易被覆盖。

ES5 提示: 在 ECMAScript 5 的严格模式下,undefined 不再是 可写的了。 但是它的名称仍然可以被隐藏,比如定义一个函数名为 undefined。
下面的情况会返回 undefined 值:

  • 访问未修改的全局变量 undefined。
  • 由于没有定义 return 表达式的函数隐式返回。
  • return 表达式没有显式的返回任何内容。
  • 访问不存在的属性。
  • 函数参数没有被显式的传递值。
  • 任何被设置为 undefined 值的变量。
    处理 undefined 值的改变

由于全局变量 undefined 只是保存了 undefined 类型实际值的副本, 因此对它赋新值不会改变类型 undefined 的值。

然而,为了方便其它变量和 undefined 做比较,我们需要事先获取类型 undefined 的值。

为了避免可能对 undefined 值的改变,一个常用的技巧是使用一个传递到匿名包装器的额外参数。 在调用时,这个参数不会获取任何值。

var undefined = 123;
(function(something, foo, undefined) {
// 局部作用域里的 undefined 变量重新获得了 `undefined` 值

})('Hello World', 42);

另外一种达到相同目的方法是在函数内使用变量声明。

var undefined = 123;
(function(something, foo) {
    var undefined;
...

})('Hello World', 42);

这里唯一的区别是,在压缩后并且函数内没有其它需要使用 var 声明变量的情况下,这个版本的代码会多出 4 个字节的代码。

译者注:这里有点绕口,其实很简单。如果此函数内没有其它需要声明的变量,那么 var 总共 4 个字符(包含一个空白字符) 就是专门为 undefined 变量准备的,相比上个例子多出了 4 个字节。

null 的用处

JavaScript 中的 undefined 的使用场景类似于其它语言中的 null,实际上 JavaScript 中的 null 是另外一种数据类型。

它在 JavaScript 内部有一些使用场景(比如声明原型链的终结 Foo.prototype = null),但是大多数情况下都可以使用 undefined 来代替。


自动分号插入

尽管 JavaScript 有 C 的代码风格,但是它不强制要求在代码中使用分号,实际上可以省略它们。

JavaScript 不是一个没有分号的语言,恰恰相反上它需要分号来就解析源代码。 因此 JavaScript 解析器在遇到由于缺少分号导致的解析错误时,会自动在源代码中插入分号。

var foo = function() {
} // 解析错误,分号丢失
test()

自动插入分号,解析器重新解析。

var foo = function() {
}; // 没有错误,解析继续
test()

自动的分号插入被认为是 JavaScript 语言最大的设计缺陷之一,因为它能改变代码的行为。

工作原理

下面的代码没有分号,因此解析器需要自己判断需要在哪些地方插入分号。

(function(window, undefined) {
        function test(options) {
        log('testing!')

    (options.list || []).forEach(function(i) {

    })

    options.value.test(
        'long string to pass here',
        'and another long string to pass'
    )

    return
    {
        foo: function() {}
    }
}
window.test = test

})(window)

(function(window) {
window.someLibrary = {}
})(window)

下面是解析器”猜测”的结果。

(function(window, undefined) {
function test(options) {

    // 没有插入分号,两行被合并为一行
    log('testing!')(options.list || []).forEach(function(i) {

    }); // <- 插入分号

    options.value.test(
        'long string to pass here',
        'and another long string to pass'
    ); // <- 插入分号

    return; // <- 插入分号, 改变了 return 表达式的行为
    { // 作为一个代码段处理
        foo: function() {} 
    }; // <- 插入分号
}
window.test = test; // <- 插入分号

// 两行又被合并了
})(window)(function(window) {
window.someLibrary = {}; // <- 插入分号
})(window); //<- 插入分号

注意: JavaScript 不能正确的处理 return 表达式紧跟换行符的情况, 虽然这不能算是自动分号插入的错误,但这确实是一种不希望的副作用。
解析器显著改变了上面代码的行为,在另外一些情况下也会做出错误的处理。

前置括号

在前置括号的情况下,解析器不会自动插入分号。

log('testing!')
(options.list || []).forEach(function(i) {})

上面代码被解析器转换为一行。

log('testing!')(options.list || []).forEach(function(i) {})

log 函数的执行结果极大可能不是函数;这种情况下就会出现 TypeError 的错误,详细错误信息可能是 undefined is not a function。

结论

建议绝对不要省略分号,同时也提倡将花括号和相应的表达式放在一行, 对于只有一行代码的 if 或者 else 表达式,也不应该省略花括号。 这些良好的编程习惯不仅可以提到代码的一致性,而且可以防止解析器改变代码行为的错误处理。

其它


setTimeout 和 setInterval

由于 JavaScript 是异步的,可以使用 setTimeout 和 setInterval 来计划执行函数。

注意: 定时处理不是 ECMAScript 的标准,它们在 DOM (文档对象模型) 被实现。
function foo() {}
var id = setTimeout(foo, 1000); // 返回一个大于零的数字
当 setTimeout 被调用时,它会返回一个 ID 标识并且计划在将来大约 1000 毫秒后调用 foo 函数。 foo 函数只会被执行一次。

基于 JavaScript 引擎的计时策略,以及本质上的单线程运行方式,所以其它代码的运行可能会阻塞此线程。 因此没法确保函数会在 setTimeout 指定的时刻被调用。

作为第一个参数的函数将会在全局作用域中执行,因此函数内的 this 将会指向这个全局对象。

function Foo() {
this.value = 42;
this.method = function() {
    // this 指向全局对象
    console.log(this.value); // 输出:undefined
};
setTimeout(this.method, 500);
}
new Foo();

注意: setTimeout 的第一个参数是函数对象,一个常犯的错误是这样的 setTimeout(foo(), 1000), 这里回调函数是 foo 的返回值,而不是foo本身。 大部分情况下,这是一个潜在的错误,因为如果函数返回 undefined,setTimeout 也不会报错。
setInterval 的堆调用

setTimeout 只会执行回调函数一次,不过 setInterval - 正如名字建议的 - 会每隔 X 毫秒执行函数一次。 但是却不鼓励使用这个函数。

当回调函数的执行被阻塞时,setInterval 仍然会发布更多的回调指令。在很小的定时间隔情况下,这会导致回调函数被堆积起来。

function foo(){
    // 阻塞执行 1 秒
}
setInterval(foo, 100);

上面代码中,foo 会执行一次随后被阻塞了一秒钟。

在 foo 被阻塞的时候,setInterval 仍然在组织将来对回调函数的调用。 因此,当第一次 foo 函数调用结束时,已经有 10 次函数调用在等待执行。

处理可能的阻塞调用

最简单也是最容易控制的方案,是在回调函数内部使用 setTimeout 函数。

function foo(){
// 阻塞执行 1 秒
    setTimeout(foo, 100);
}
foo();

这样不仅封装了 setTimeout 回调函数,而且阻止了调用指令的堆积,可以有更多的控制。 foo 函数现在可以控制是否继续执行还是终止执行。

手工清空定时器

可以通过将定时时产生的 ID 标识传递给 clearTimeout 或者 clearInterval 函数来清除定时, 至于使用哪个函数取决于调用的时候使用的是 setTimeout 还是 setInterval。

var id = setTimeout(foo, 1000);
clearTimeout(id);

清除所有定时器

由于没有内置的清除所有定时器的方法,可以采用一种暴力的方式来达到这一目的。

// 清空"所有"的定时器
for(var i = 1; i < 1000; i++) {
    clearTimeout(i);
}

可能还有些定时器不会在上面代码中被清除(译者注:如果定时器调用时返回的 ID 值大于 1000), 因此我们可以事先保存所有的定时器 ID,然后一把清除。

隐藏使用 eval

setTimeout 和 setInterval 也接受第一个参数为字符串的情况。 这个特性绝对不要使用,因为它在内部使用了 eval。

注意: 由于定时器函数不是 ECMAScript 的标准,如何解析字符串参数在不同的 JavaScript 引擎实现中可能不同。 事实上,微软的 JScript 会使用 Function 构造函数来代替 eval 的使用。

function foo() {
    // 将会被调用
}

function bar() {
    function foo() {
        // 不会被调用
    }
    setTimeout('foo()', 1000);
}
bar();

由于 eval 在这种情况下不是被直接调用,因此传递到 setTimeout 的字符串会自全局作用域中执行; 因此,上面的回调函数使用的不是定义在 bar 作用域中的局部变量 foo。

建议不要在调用定时器函数时,为了向回调函数传递参数而使用字符串的形式。

function foo(a, b, c) {}

// 不要这样做
setTimeout('foo(1,2, 3)', 1000)

// 可以使用匿名函数完成相同功能
setTimeout(function() {
    foo(1, 2, 3);
}, 1000)

注意: 虽然也可以使用这样的语法 setTimeout(foo, 1000, 1, 2, 3), 但是不推荐这么做,因为在使用对象的属性方法时可能会出错。 (译者注:这里说的是属性方法内,this 的指向错误)

结论

绝对不要使用字符串作为 setTimeout 或者 setInterval 的第一个参数, 这么写的代码明显质量很差。当需要向回调函数传递参数时,可以创建一个匿名函数,在函数内执行真实的回调函数。

另外,应该避免使用 setInterval,因为它的定时执行不会被 JavaScript 阻塞

转载自:[http://bonsaiden.github.io/JavaScript-Garden/zh/#object.prototype](http://bonsaiden.github.io/JavaScript-Garden/zh/#object.prototype)

Interview

前端面试详解一

1.标签上title与alt属性的区别是什么?
title是设置鼠标移动到图片上时显示的内容,而alt是用于当图片没有正常显示时出现的提示文字,另外alt还用于在seo中针对图片的优化说明.
2.

标签内的文字是什么颜色的?

  <style>
      .classA{color:blue !important;}
      .classB{color:red;}
      </style>
 <body>
       <p class=‘classB classA’ style=‘color:green’>123</p>
  </body>
答:文字是blue蓝色,! important ,可以更改默认的CSS样式优先级规则,使该条样式属性声明具有最高优先级
3. css单位px,em,rem的区别。

px像素(Pixel)。相对长度单位。像素px是相对于显示器屏幕分辨率而言的px的特点:

1. IE无法调整那些使用px作为单位的字体大小;
2. 国外的大部分网站能够调整的原因在于其使用了em或rem作为字体单位;
3. Firefox能够调整px和em,rem,但是96%以上的中国网民使用IE浏览器(或内核)。
em是相对长度单位。相对于当前对象内文本的字体尺寸。如当前对行内文本的字体尺寸未被人为设置,则相对于浏览器的默认字体尺寸

EM特点

1. em的值并不是固定的;
2. em会继承父级元素的字体大小。
rem是CSS3新增的一个相对单位(root em,根em),这个单位引起了广泛关注。这个单位与em有什么区别呢?区别在于使用rem为元素设定字体大小时,仍然是相对大小,但相对的只是HTML根元素。这个单位可谓集相对大小和绝对大小的优点于一身,通过它既可以做到只修改根元素就成比例地调整所有字体大小,又可以避免字体大小逐层复合的连锁反应。目前,除了IE8及更早版本外,所有浏览器均已支持rem。对于不支持它的浏览器,应对方法也很简单,就是多写一个绝对单位的声明。这些浏览器会忽略用rem设定的字体大小。
4.DOMContentLoaded 与 OnLoad事件的区别。
onload会在html,css,js,图片等资源文件加载完成后执行里面的代码,而DOMContentLoaded则只需在html,css,js加载完成后就执行里面的代码。
5.“==”和“===”的不同。
==用于一般比较,===用于严格比较,==在比较的时候可以转换数据类型,===严格比较,只要类型不匹配就返回flase
6.JavaScript的数据类型都有哪些?
JavaScript中有5种简单数据类型(也称为基本数据类型):Undefined、Null、Boolean、Number和String。还有1种复杂数据类型——Object,Object本质上是由一组无序的名值对组成的
7. var foo = “11” + 2 - “1”;
 foo 输出的是?
 typeof foo输出的是?
foo输出的是111,首先字符串11+数字2,执行字符串连接为112,而后减1,是将112执行隐式类型转换为数字112后再减1,所以最后结果是111
typeof foo,为number
8.split() join()的区别在哪?
split是将字符串分割为数组,而join是将数组中的值连接为字符串
9.数组方法pop() push() unshift() shift()的区别在哪?
* pop是删除数组最后一位
* push是在数组最后一位插入一个新元素
* unshift是在数组开头添加一个或更多元素
* shift是将数组开头的元素删除
10.JavaScript的事件流模型都有哪些?
在各种浏览器中存在三种事件模型:原始事件模型( original event model),DOM2事件模型,IE事件模型.

原始事件模型简单来说就是用on+事件名的方式绑定的事件,比如onclick,onsubmit等

dom2事件模型就是通过addEventListener绑定的事件,可以通过event.stopPropagation()来停止事件的传播,调用preventDefault()来阻止浏览器的默认行为

IE模型也提供了一个event对象封装了事件的详细信息,但是IE不把该对象传入事件处理函数,由于在任意时刻只会存在一个事件,所以IE把它作为全局对象window的一个属性,IE中的事件传播模式对应于DOM2的第二和第三阶段,首先执目标元素的处理函数,然后向上传播到达document,ie中只能能捕捉鼠标事件,而DOM2中可以捕捉所有的事件,IE中注册和删除事件处理函数的方法也不同于DOM2.

事件处理函数的注册和删除是通过元素的

* attachEvent( "eventType","handler") and
* detachEvent("eventType","handler" ),与dom2不同的是eventType有on前缀
11.请按执行顺序写出几次alert()语句a的值:
function fn1() {
    var a = 1;
    function fn2() {
        alert(a++);
    }
    return fn2;
}
var f = fn1();
f();
f();
两次:第1次1,第二次2,var f=fn1()的时候只是调用了fn1这个函数并将fn2作为返回值返回,并未执行fn2里面的内容.

在第一个f()的时候就是执行fn2里面的内容,同时因为fn2是一个闭包函数,调用了fn1中的a,所以a为1,至于a++,因为a++为后自加,先引用a,再进行自加操作,所以第一次弹出为1,而第二个f(),因为a的值已经++过了,所以弹出2.
12.请按要求操作数组a:
i. 如何删除数组a中第2个元素(即:“B”)?
var a = [“A”, “B”, “C”, “D”];
a.splice(1,1);

ii. 如何删除数组a中最后一个元素?
var a = [“A”, “B”, “C”, “D”];
a.pop()

iii. 如何将“NEW”插入数组a的“B”和“C”之间?
var a = [“A”, “B”, “C”, “D”];
a.splice(2,0,”new”);
13.以下技术您用到过哪些?
EasyUI  BootStrap  jQuery   jQuery Mobile  zepto  ExtJS
angular react  backbone  redux grunt  gulp  seats nodeJS
swiper web pack requireJS
略
14.var stringArray = [“Hello”, “World”],给出您能想到的最好的方法输出“Hello World”。

stringArray.join(“ ”);

15.typeof输出的类型有哪些
A.number  B.string C.null  D.undefined
16.以下关于Array数组对象的说法不正确的是(c)
A.对数组里数据的排序可以用sort函数,如果排序效果非预期,可以给sort函数加一个排序函数的参数。
B.reverse用于对数组数据的倒序排列。
C.向数组的最后位置加一个新元素,可以用pop方法。
D.unshift方法用于向数组删除第一个元素。
17.以下哪些是css伪类?(c,d)after和before是伪元素
A.after 

B.before 

C.hover 

D.nth-child
18.javascrip的基本数据类型有哪些?(a,b,e,f,g)
A.number 

    B.string  

C.object  

D.array  

E.null 

F.undefined  

G.boolean
19.以下不是html5元素的有哪些?(b,c)
A.footer

B.center 

C.font 

D.frame
20.下面有关浏览器中使用js跨域获取数据的描述。说法错误的是?a
A.域名,端口相同,协议不同,属于相同的域。

B.js可以使用jsonp进行跨域。

C.通过修改document.domain来跨子域。

D.使用window.name来进行跨域。
21.下述有关css属性position的属性值的描述。 语法错误的是?b
static:没有定位,元素出现在正常的流中。

fixed:生成绝对定位的元素,相对于父元素进行定位。

relative:生成相对定位的元素,相对于元素本身正常位置进行定位。

absolute:生成绝对定位的元素,相对于static定位意外的第一个祖先元素。
22.CSS实现隐藏页面元素的方法有哪些?
display:none; 

visibility: hidden;

opacity:0;  

position:absolute;

left:-10000px;
23.请写出打印出来的值。
var flag = true;
if (flag) {
    var a = 10;
}
function fn() {
    var b = 20;
    c = 30;
}
fn();
console.log(a);
console.log(c);
console.log(b);
答:10,30,报错(b是在fn内部定义,全局并没有b这个变量)
24.xhtml和html有什么区别?
xhtml比HTML严谨性会高点,然后基本语言都还是沿用的HTML的标签,只不过废除了部分表现层的标签,同事在标准上要求高了点比如标签的严格嵌套,标签结束等等
25.每个html文件里开头都有个Doctype。这是代表了什么意思?
版本声明
26.问题 strong与em的异同?
语义都是语气加重,strong在显示上是变粗,而em是斜体显示
27.行内元素有哪些?块级元素有哪些?
块状:* <ol> 
* <ul> 
* <dl> 
* <table>
* <h1>...<h6> 
* <p> 
* <blockquote> 
* <dt> 
* <address>
* <caption> 
* <div>

内联(行内):

* a - 锚点
* abbr - 缩写
* acronym - 首字
* b - 粗体(不推荐)
* bdo - bidi override
* big - 大字体
* br - 换行
* cite - 引用
* code - 计算机代码(在引用源码的时候需要)
* dfn - 定义字段
* em - 强调
* font - 字体设定(不推荐)
* i - 斜体
* img - 图片
* input - 输入框
* kbd - 定义键盘文本
* label - 表格标签
* q - 短引用
* s - 中划线(不推荐)
* samp - 定义范例计算机代码
* select - 项目选择
* small - 小字体文本
* span - 常用内联容器,定义文本内区块
* strike - 中划线
* strong - 粗体强调
* sub - 下标
* sup - 上标
* textarea - 多行文本输入框
* tt - 电传文本
* u - 下划线
* var - 定义变量
28.css选择符有哪些?哪些属性可以继承?优先级算法如何计算?
选择符:[http://www.360doc.com/content/
14/0104/22/15643_342679505.shtml](http://www.360doc.com/content/
14/0104/22/15643_342679505.shtml)

除了这些都可以继承:display、margin、border、padding、background、height、min-height、max- height、width、min-width、max-width、overflow、position、left、right、top、 bottom、z-index、float、clear、table-layout、vertical-align、page-break-after、 page-bread-before和unicode-bidi

原则一: 继承不如指定

原则二: #id > .class > 标签选择符

原则三:越具体越强大

原则四:标签#id >#id ; 标签.class > .class

CSS优先级包含四个级别(标签内选择符,ID选择符,Class选择符,元素选择符)以及各级别出现的次数!

根据这四个级别出现的次数计算得到CSS的优先级。

CSS优先级的计算规则如下:

* 元素标签中定义的样式(Style属性),加1,0,0,0
* 每个ID选择符(如 #id),加0,1,0,0
* 每个Class选择符(如 .class)、每个属性选择符(如 [attribute=])、每个伪类(如 :hover)加0,0,1,0
* 每个元素选择符(如p)或伪元素选择符(如 :firstchild)等,加0,0,0,1

然后,将这四个数字分别累加,就得到每个CSS定义的优先级的值,
然后从左到右逐位比较大小,数字大的CSS样式的优先级就高。
29.HTML5文档类型和字符集是?
<!DOCTYPE html>
<meta charset="UTF-8">
30.HTML5中canvas是什么?datalist是什么?
canvas:绘图标签,可以使用它制作游戏和图像处理
datalist:<datalist> 标签定义选项列表。与 input 元素配合使用该元素,来定义 input 可能的值
31.请分别使用原生js,jquery获取标签信息。

(<div id=“div1”>这是一个DIV标签</div>)
document.getElementById(“div1”).innerHTML;

    document.querySelector(“#div1”).innerText;

$(“#div1”).html()
32.求下面两种情况下,a和b的值是多少?
题目1:var a = 1;  function func1(){a=b=5;} alert(a); alert(b);

题目2:var a = 1;  function func1(){var b = 1;a=b=5;}alert(a);alert(b);

题目1答:如果在alert(a)前没有调用func1的话就1,和报错,b没定义,如果调用了func1的话就是5,5

题目2答:如果在alert(a)前没有调用func1的话就1,和报错,b没定义,如果调用了func1的话就是5,和报错,因为b是在func1中定义的局部变量,在全局获取不到

######34.将数组[“a”, “b”, “c”, “d”]转化为字符串”d#c#b#a”;
[‘a’,’b’,’c’,’d’].join(“#”)

35.对应写出下列输出值。
alert(typeof(null))
alert(typeof(NaN))
alert(typeof(undefined))
alert(typeof(“undefined”))
alert(NaN == undefined)
alert(NaN == NaN)

答:
alert(typeof(null))//object
alert(typeof(NaN))//number
alert(typeof(undefined))//undefined
alert(typeof("undefined"))//string
alert(NaN == undefined)//false
alert(NaN == NaN)//false
36.获取enent目标对象的方法(要求兼容性)。
document.onclick = function (ev){
var e = ev||window.event;
}
37.经常使用的页面开发工具以及测试工具。
sublime,hbulider,webstorm,测试工具:mocha
38.经常使用什么脚本库,开发或使用过什么应用或组建。
jquery,zepto
39.请写出代码执行结果
var a;
alert(a);                   ___undefined_________
alert(typeof a);        undefined______________
alert(b);                  报错_________________
40.旧版本的IE浏览器(如:IE6,IE7)的默认盒模型,W3C定义的合模型,二者之间有什么不同?
ie的width是包含border和padding的,也称为怪异模式,触发条件是在没写版本声明的时候

而w3c的width就width不含border和padding
41.以下HTML代码片段,有几种方法让该div在浏览器中横向居中?
<div style=“width:200px;height:100px;”></div>

第一种:<div style=“width:200px;height:100px; margin:    0 auto”></div>

第二种:<div style="width:200px;height:100px; position:absolute; left:50%; margin-left:-100px;”></div>

第三种:<div style="width:200px;height:100px; position:absolute; left:50%; transform:translate(-50%,0)"></div>
42.以下css代码片段,在IE6,IE7,IE8,Firefox,Chrome下分别呈现什么颜色?
.browers{

background-color:#000000;/*黑色*/

background-color:#ff0000\9;/*红色*/

+background-color:#00ff00;/*绿色*/

_background-color:#0000ff;/*蓝色*/

}
在firefox和chrome中显示的是黑色

在ie8下是红色(\9是ie8的hack)

在ie7下是绿色(+是ie7的hack)

在ie6下是蓝色(_是ie6转有hack)
43.如何做到让一个div隐藏但是却在html流中占位?
visibility: hidden;和opacity:0;
44.请写一段json字符串,包含所有的数据类型.
{"a":"string","b":5,"c":[1,2,3,4],"d":{"e":"obj"}}
45.请按执行顺序写出几次alert()语句a的值:
var a = 10;
function fn() {
    alert(a);
    var a = 100;
    alert(a);
}
fn();
alert(a);

undefined,100,10
46.请按执行顺序写出几次alert()语句a的值:
function fn1(){

var a = 1;

function fn2(){
    alert(a++);
}

fn2();
}
fn1();
fn1();

1,1
47.下面的输出是?
<style>
.red{color:red;}
.blue{color:blue;}
</style>

<p class=“blue red”></p>
蓝色
48.怎样添加,移除,移动,复制,创建和查找节点?
appendChild,
removeChild,
replaceChild,
cloneNode,
createElement,
getElementById(ByTagName)
49.用js写一个正则匹配标签中是否包含一个class?
var className = "active";
var reg = new RegExp(“\\b"+className+"\\b");
50.阅读以下代码回答:hello world 是什么颜色?
<style>
    .classA{color:red;}
    .classB{color:yellow;}
</style>
<p class=“classB classA”>hello world</p>
yellow
51.完善以下代码:
<div style=“height:50px;” id=“content”>
<p>铝神公司</p>
</div>
(1).请用CSS实现”铝神公司”在div内水平垂直居中?
(2).请用javascript将字体设置为红色.
答:(1)
第一种写法:
    *{
            margin: 0;
    }
    div{
        text-align: center;
        line-height: 50px;
    }
第二种写法:
    *{
        margin: 0;
    }
    div{
        display: table;
        width: 100%;
    }
    div p{
        display: table-cell;
        width: 100%;
        text-align: center;
        vertical-align: middle;
    }
第三种写法:
    *{
        margin: 0;
    }
div{
    position: relative;
}
div p{
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%,-50%);
}
(2)
    第一种:content.style.color=“red”,高级浏览器id不用获取
    第二种:  document.getElementById(“content”).style.color=“red”
    第三种:document.querySelector(“#content").style.color="red";
52.写一个函数,将一串字符串中空格替换”.”,如replace(“铝 神 欢 迎 你”)=“铝.神.欢.迎.你”
第一种:
    function re(s){
        return s.replace(/\s/g,".");
    }
第二种:
    function re(s){
        return s.split(" ").join(".");
    }
53.请用CSS实现4个div的水平排列占满一行,要求4个div宽度相同,有10px的padding值.
div{
    float: left;
    width: 25%;
    height: 100px;
    background-color: #ccc;
    padding: 10px;
    box-sizing:border-box;
}
54.对作用域上下文和this的理解,看下列代码:
var  User =  {

  count:1,

 getCount:function(){

return this.count;

}
};
console.log(User.getCount());//??

var func = User.getCount;

    console.log(func());//??

1,undefined
55.<!DOCTYPE html>中DOCTYPE有什么作用,去掉会有什么影响?
声明文档类型,在ie6下如果去掉会进入怪异模式,width包含padding和border
56.HTML行内元素有哪些,块级元素有哪些?画出CSS盒模型。
上面有
57.内联和important哪个优先级更高?
important更高
58.清除浮动的几种方式?
第一种:clear:both;
第二种:overflow:hidden;
第三种:父级一起浮动
第四种:父级定义:after伪元素配合zoom解决ie6,7
第五种:父级加position:absolute
59.HTML5存储类型有哪些?
cookies:存储大小为4k
localstorage:以键值对(Key-Value)的方式存储,永久存储,永不失效,除非手动删除,大小为5m
sessionstorage:HTML5 的本地存储 API 中的 localStorage     与 sessionStorage 在使用方法上是相同的,区别在于 sessionStorage 在关闭页面后即被清空,而 localStorage 则会一直保存
离线缓存(application cache):本地缓存应用所需的文件
Web SQL:关系数据库,通过SQL语句访问
Web SQL 数据库 API 并不是 HTML5 规范的一部分,但是它是一个独立的规范,引入了一组使用 SQL 操作客户端数据库的 APIs。
60.HTTP状态码有哪些,分别代表什么意思?
如果是1开头的代表请求已被接受,需要继续处理
如果是2开头这一类型的状态码,代表请求已成功被服务器接收、理解、并接受
如果是3开头这类状态码代表需要客户端采取进一步的操作才能完成请求
如果是4开头这类的状态码代表了客户端看起来可能发生了错误,妨碍了服务器的处理
如果是5开头这类状态码代表了服务器在处理请求的过程中有错误或者异常状态发生,也有可能是服务器意识到以当前的软硬件资源无法完成对请求的处理
61.CSS3新增伪类选择器有哪些?
:first-of-type    p:first-of-type    选择属于其父元素的首个 <p> 元素的每个 <p> 元素。 

:last-of-type    p:last-of-type    选择属于其父元素的最后 <p> 元素的每个 <p> 元素。

:only-of-type    p:only-of-type    选择属于其父元素唯一的 <p> 元素的每个 <p> 元素。

:only-child    p:only-child    选择属于其父元素的唯一子元素的每个 <p> 元素。

:nth-child(n)    p:nth-child(2)    选择属于其父元素的第二个子元素的每个 <p> 元素。 

:nth-last-child(n)    p:nth-last-child(2)    同上,从最后一个子元素开始计数。

:nth-of-type(n)    p:nth-of-type(2)    选择属于其父元素第二个 <p> 元素的每个 <p> 元素。 

:nth-last-of-type(n)    p:nth-last-of-type(2)    同上,但是从最后一个子元素开始计数。   

:last-child    p:last-child    选择属于其父元素最后一个子元素每个 <p> 元素。  

:root    :root    选择文档的根元素。

:empty    p:empty    选择没有子元素的每个 <p> 元素(包括文本节点)。  

:target    #news:target    选择当前活动的 #news 元素。

:enabled    input:enabled    选择每个启用的 <input> 元素。  

:disabled    input:disabled    选择每个禁用的 <input> 元素  

:checked    input:checked    选择每个被选中的 <input> 元素。  

:not(selector)    :not(p)    选择非 <p> 元素的每个元素。

::selection    ::selection    选择被用户选取的元素部分。
62.用CSS3编写:div旋转90度,0.5秒后变圆,0.5秒后方法两倍。
63.举例2种强制类型转换和1种隐式类型转换?
强制(parseInt,parseFloat,number)
隐式(== - ===)
64.split() 和join()的区别
split是将字符串切割为数组,而join是将数组连接为字符串
66.事件绑定和普通事件有什么区别
区别就是普通事件只支持单个事件,而事件绑定可以添加多个事件
67.ajax请求的时候get和post方式的区别
第一:ajax.open方法的第二个参数如果是get请求路径中是带有参数的,而post就只是路径

第二:如果是post方式在open方法和send方法之间还要设置请求的头:ajax.setRequestHeader("Content-Type", “application/x-www-form-urlencoded");

第三:ajax.send方法如果是get的话参数留空就行,而post需要将参数以序列化的格式传入
68.call和apply的区别
call和apply都是改变函数内部的this指向,第一个参数也都放的是要改变this对象,区别主要在第二个参数上,call从第二个开始放的是正常的参数,而apply放的是数组
69.事件委托是什么,使用事件委托有什么好处?
就是让别人来做,这个事件本来是加在某些元素上的,然而你却加到别人身上来做,完成这个事件。

也就是:利用冒泡的原理,把事件加到父级上,触发执行效果。

好处呢:1,提高性能。
70.如何阻止事件冒泡和默认事件
阻止事件冒泡:ie:window.event.cancelBubble=true;而非ie:e.stopPropagation();

阻止默认事件:ie:window.event.returnValue = false;return false;而非ie:e.preventDefault();
72.跨域的方法
1> document.domain + iframe      (只有在主域相同的时候才能使用该方法)

2> window.name + iframe

3>html5中的postMessage()

4>CORS

5>jsonp

6>websocket

跨域支持post请求的:

1>CORS

2>window.postMessage

3>html5的websocket

4>iframe http://www.jianshu.com/p/4773501f1adf

5>flash proxy
73.编写一个数组去重的方法
Array.prototype.unique3 = function(){
 var res = [];
 var json = {};
 for(var i = 0; i < this.length; i++){
  if(!json[this[i]]){
   res.push(this[i]);
   json[this[i]] = 1;
  }
     }
 return res;
}
74.Ajax请求Json数据后如何解析
第一种:使用eval
第二种:使用JSON.parse()
第三种:new Function("return" + strJSON)();
75.如何实现多标签页之间的通讯。
调用 localstorge、cookies 等本地存储方式
76.简述什么是按需加载,然后实现一个图片按需加载的js。
图片的按需加载又称为延时加载,随着用户的滚动条滑动来按需获取图片。代码略,课件中有
77.简述下闭包,有什么好处和坏处。
闭包的好处有:
1.缓存
2.面向对象中的对象
3.实现封装,防止变量跑到外层作用域中,发生命名冲突
4.匿名自执行函数,匿名自执行函数可以减小内存消耗

闭包的坏处:
1.内存消耗
2.性能差
78.什么是模块开发,怎么实现一个模块开发。
一个模块就是实现特定功能的文件,有了模块,我们就可以更方便地使用别人的代码,想要什么功能,就加载什么模块。
可以使用模块加载器比如sea.js或者require.js
79.实现一个多列的等高布局。

80.zoom是什么,有什么用?
Zoom属性是IE浏览器的专有属性,它可以设置或检索对象的缩放比例,比如触发ie的hasLayout属性,清除浮动、清除margin的重叠等。
81.怎么优化一个首屏的加载速度,可以从js和css等多方便考虑
可以
1.可以使用延迟加载,通过js将第二屏出现的图片设置问延迟加载,优先加载第一屏的图片,如果不想自己写js推荐一个lazyload这个插件
2.尽可能压缩图片大小,可以利用在线图片压缩工具tinypng
3.将JS放到HTML最后的地方,能最后加载,优先保证基础信息阅读。可以参考雅虎优化原则
83.怎么实现一个对象的继承。
Object.prototype.extend = function(obj){
 //在函数里,把obj属性复制到自身
 for(var k in obj){
      if(obj.hasOwnProperty(k)){
       if(this[k] == undefined){
    this[k] = obj[k];
       }
  }
 }
}
var kitty = {color:'yellow',climb:function()    {alert('我会爬树');}};
var tiger = {color:'yellow and black'};
tiger.extend(kitty);
tiger.climb();
84.简述下平时开发用到的技术栈。
作者:何幻

链接:http://www.zhihu.com/question/33179506/answer/    75015013

来源:知乎

著作权归作者所有,转载请联系作者获得授权。

浏览器环境IE6~8 IE9+ Chrome Firefox Safari Opera Edge

HTTP协议URI Cache Session Cookie Request Response

HTMLW3C HTML4.01 HTML5 DOM 语义化

CSSCSS1~3 选择器 盒模型 Flex 媒体检测 响应式 渲染引擎

JavaScriptEcmaScript3~5~6 Lexical-scope prototype-chain AJAX js引擎

编辑器Emacs Vim WebStorm Atom Sublime-Text

发布部署合并 压缩 单元测试 Node.js Grunt Gulp Yeoman Phantom JSLint

框架类库jQuery Zepto UnderScore Backbone Knockout React AngularJS

模块管理CommonJS Webpack
UI框架BootStrap SemanticUI jQueryUI Foundation
推送技术WebSocket 轮询 长连接
跨域技术iframe JSONP CORS
数据可视化D3 Echarts HighCharts Canvas
异步编程Promise $.Deferred Generator
CSS预处理器LESS SASS Stylus
客户端模板Handlebars Haml Jade Mustache
相关语言CoffeeScript TypeScript Dart WebAssembly
WebApp/PCReact-native ionic
85.css选择器中,以下排序正确的为(a)
A、行内样式>id>class>标签
B、行内像是>class>id>标签
C、id>class>行内样式>标签
D、标签>class>id>行内样式
86.在JavaScript中,以下哪个变量名是非法的(b)
A、Name    B、9name    C、Name_a    D、Name9
87.请选择结果为真的表达式(c)
A、null instance Object    B、null === undefined        C、null == undefined        D、NaN == NaN
88.var a = new Object();
a.value = 1;
b = a;
b.value = 2;
console.log(a.value);
输出正确的是:(b)
A、1        B、2        C、undefined    D、以上都不正确
89. var a = 3;
(function() {
window.a = 2
    var a = 1;
})()
console.log(a)
输出为:(b)
A、1        B、2        C、3        D、undefined
91.

标签内的文字颜色是什么?

<style>
.classA{color:blue !important;}
.classB{color:red;}
</style>
<body>
    <p class=‘classB classA’ style=‘color:green’> 123 </p>
</body>

蓝色

100.微信分享怎么实现
分为3种:
第一种使用微信默认分享,分享标题为分享页的title标签的内容,如果是分享给朋友,描述中为网页链接,分享小图标为网页中第一张符合大小的图片

第二种是使用微信的jssdk,wx.onMenuShareTimeline分享到朋友圈.和wx.onMenuShareAppMessage分享给朋友。

第三种如果是做微信广告,部署到腾讯服务器下,可以使用WeixinJSBridge调用分享接口
105.ajax出问题时怎么判断是前端的问题还是后台的
首先测试api是否能正常调用,比如可以模拟一些数据发送一下,如果是get请求就直接浏览器地址栏中拼接上然后看返回结果。在确认api调用正常的情况下,基本就可以确定是属于前端的问题。

技术方面问题:

108.如何解决在移动端1px的线比实际粗
1.设置为0.5px
2.判断如果在retina屏上就使用tranform对它进行缩放
@media screen and (-webkit-min-device-pixel-ratio: 2){
.radius-border:before{
    content: "";
    pointer-events: none; /* 防止点击触发 */
    box-sizing: border-box;
    position: absolute;
    width: 200%;
    height: 200%;
    left: 0;
    top: 0;
    border-radius: 8px;
    border:1px solid #999;
    -webkit-transform(scale(0.5));
    -webkit-transform-origin: 0 0;
    transform(scale(0.5));
    transform-origin: 0 0;
}
}
109.如何计算移动端rem的值
如果html的font-size为10px,2rem就是20px.
110.ajax请求到显示流程
1.创建ajax对象
2.调用open方法
3.调用send方法
4.添加onreadystatechange事件
5.判断返回的状态码是否为成功的状态码
6.通过调用ajax的responesText属性返回数据
113.向后台请求数据,有几种方式
用的最多的就是get和post.
除了这两个还有:
HEAD
类似于get请求,只不过返回的响应中没有具体的内容,用于获取报头
PUT
传说当前请求文档的一个版本
DELETE
发送一个用来删除指定文档的请求
TRACE
发送请求的一个副本,以跟踪其处理进程
OPTIONS
返回所有可用的方法;可检查服务器支持哪些方法
CONNECT
用于ssl隧道的基于代理的请求
121.以下localStroage的使用方式,哪些是正确的?abe
A.localStorage. setItem(“lastname”, “smith”);
        B.document.getElementById(“result”).innerHTML=loaclStorage.getItem(“lastname”);
C.localStorage.lastname=“Smith”;
D.document.getElementByid(“result”).innerHTML=localStorage.lastname;
E.localStorage.removeItem(“lastname”);
122.以下关于Array数组对象的说法不正确的是?c
A.对数组里数据的排序可以用sort函数,如果排序效果非预期,可以给sort函数加一个排序函数的参数.
B.reverser用于对数组数据的排序排列.
C.向数组的最后位置加一个新元素,可以用pop方法.
D.unshift方法用于向数组删除第一个元素.
123.var stringArray=[“Hello”,”World”],给出您能想到的最好的方法输出”Hello, World”
stringArray.join(“,”);
很惭愧<br><br>只做了一点微小的工作<br>谢谢大家