JSLecture
outline
1 JS常见使用场景
2 JS的重要概念(包含IIEF、JS原型链、this(call/apply)、闭包)最后我们结合闭包和call函数完成一个类型检测
3 浏览器端的javascript(包括原生DOM操作、用模板来完成原生DOM操作、jQuery操,基于此我们完成一个简单的单页应用)
4 其他
JS常见使用场景
a. 使用jQuery修改DOM1
2
3
4
5<script src="path/to/jQuery"></script>
<script>
$("#id").html("");
...
</script>b. 使用jQuery插件
1
2
3
4
5<script src="path/to/jQuery"></script>
<script scr="path/to/jQuery-customPlugin.js"></script>
<script>
//your custom code
</script>c. 使用bootstrap
1
2
3
4<link rel="stylesheet" href="path/to/bootstrap.min.css">
any element
<script src="path/to/jQuery"></script>
<script src="path/to/Bootstrap.js"></script>d. 使用jQuery做动画(下拉菜单)
1
2
3
4
5
6
7
8
9
10
11<ul id="mylist">
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
</ul>
<script src="path/to/jQuery"></script>
<script>
$("#mylist").slideToggle('normal');
</script>e. 使用ajax
1
2
3
4
5
6
7
8
9
10$.ajax({
url:'targetUrl',
data:{
key1:value1,
key2:value2
}
success:function(data){
}
});f. 其他
这里例举了一些常用的库
echarts
d3
normalize.css
fontawesome
underscoreJS的重要概念
a. IIEFIIEF(immedite invoke express function),常见如下形式:
1
2
3(function($){
})(jQuery)b. JS原型链
1
2
3
4
5
6
7
8//假设一个对象o,自身有属性a、b
//o的原型有属性b、c
//o的原型的原型指向object
var o = {a: 1, b: 2};
o.prototype = {
b : 3,
c : 4,
}整个原型链如下:
原型链在使用过程中,一层层查找,优先使用当前对象的属性。1
2
3
4console.log(o.a); // 1
console.log(o.b); // 2 "属性屏蔽 (property shadowing)"
console.log(o.c); // 4
console.log(o.d); // undefinedJS里的继承、特性都是基于原型链.
c. this(call/apply)
JS里的this跟其他语言不太相同,JS中的this总是指向一个对象,但是具体指向那个对象在运行是决定的.JS中this的指向给我们会带来很多的苦恼.
对象中this1
2
3
4
5
6
7
8
9
10
11
12window.a = 2;
var obj = {
a : 1,
getA : function(){
console.log(this);
console.log(this.a);
}
}
obj.getA();//1
var _getA = obj.getA;
_getA();//2函数中的this
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15<html>
<body>
<div id="div1">我是div</div>
</body>
<script>
window.id = "window";
document.getElementById("div1").onclick = function(){
console.log(this.id);//div1
var callback = function(){
console.log(this.id);//window
}
callback();
}
</script>
</html>解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16<html>
<body>
<div id="div1">我是div</div>
</body>
<script>
window.id = "window";
document.getElementById("div1").onclick = function(){
console.log(this.id);//div1
var _this = this;
var callback = function(){
console.log(_this.id);//div1
}
callback();
}
</script>
</html>this的指向在运行时才会确定,控制的难度略大,但是在上面的例子中,我们通过强行保存this的指向避免this乱指的问题,但是这样的方式未免不会有点过于生硬.这里我们通过apply/call来提供另外一种解决方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17<html>
<body>
<div id="div1">我是div</div>
</body>
<script>
window.id = "window";
document.getElementById("div1").onclick = function(){
console.log(this.id);//div1
var callback = function(){
console.log(this.id);//div1
}
callback.call(this);
//或者
//callback.apply(this);
}
</script>
</html>这里call/apply被我们用来修正this的指向.
除了修正this的指向之外,我们还可以用call/apply做一些有意思的事情,比如”借用”方法/继承.(补充一下,apply和call方法的作用类似,apply和call的第一个参数都是一个对象,但是apply的后面的传参方式是把要传入的参数构造成一个数组,而call的话,就把参数一个一个传入就可以了.A(pply) for Array and C(all) for Comma)1
2var arr = [1,4,3];
arr.push(5)对于Array类上的push方法,Object也是可以借用的
1
2
3var o = {};
Array.prototype.push.call(o,1);
Array.prototype.push.call(o,2);//Object {0: 1, 1: 2, length: 2}借用方法的时候要找个类似的对象上的方法借用
1
2var func = function(){}
Array.prototype.push.call(func,"fun");//Uncaught TypeError: Cannot assign to read only property 'length' of function 'function (){}'
d. 闭包
这里讲的闭包主要是说的变量的作用域以及生存周期
变量作用域(作用域链)
JS中的函数可以创造作用域.函数内部可以看到外部的外部的变量,但函数外部却无法看到函数内部的变量.在JS中,如果要是用某个变量,如果在当前函数(作用域)没有找到,会自动顺着找到函数的外部,一直到到全局对象window为止.
这和C++/JAVA很不同,如果函数中的没有找到某个局部变量,会报错.1
2
3
4
5
6
7
8
9
10
11
12var a = 1;
var func1 = function(){
var b = 2;
var func2 = function(){
var c = 3;
console.log(b);//2
console.log(a);//1
}
func2();
console.log(c);//undefined;
}
func1();JS中这条作用域链也会造成很大的麻烦,尤其是很容易出现
a = 100;
这种代码.这样相当于window.a = 100;
,作用域的问题造成了JS的代码的可维护性降低,对应的也有sea.js
这样的JS模块加载器,我们只要按照commonjs规范写代码,并利用export把方法、变量暴露出去,就可以减小变量冲突的风险.提升JS组件、功能模块可维护性.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22<html>
<head>
<meta charset="UTF-8">
<title>closure</title>
</head>
<body>
<div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>
<div>5</div>
<script>
var nodes = document.getElementsByTagName("div");
for(var i = 0; i < nodes.length; i++){
nodes[i].onclick = function(){
console.log(i);
}
}
</script>
</body>
</html>这里不管点击哪一个div,都会输出5.原因在于点击事件是异步触发的,当用户点击div的时候,for循环早就执行完了,此时i的值是5,当用户点击了某个div的时候,div顺着onclick函数的作用域链,找到i(5),并输出.解决方法,用function来构造一个闭包的环境,让i的值(1…4)传入一个立即执行函数,让i值存活在闭包的里.当异步调用的时候,可以找到这个i的值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24<html>
<head>
<meta charset="UTF-8">
<title>closure</title>
</head>
<body>
<div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>
<div>5</div>
<script>
var nodes = document.getElementsByTagName("div");
for(var i = 0; i < nodes.length; i++){
(function(i){
nodes[i].onclick = function(){
console.log(i);
}
})(i);
}
</script>
</body>
</html>另一种闭包
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24<html>
<head>
<meta charset="UTF-8">
<title>closure</title>
</head>
<body>
<div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>
<div>5</div>
<script>
var nodes = document.getElementsByTagName("div");
for(var i = 0; i < nodes.length; i++){
nodes[i].onclick = function(i){
return function(e){
console.log(i);
}
}(i);
}
</script>
</body>
</html>d. 应用(call+闭包):
类型检测
由于JS本身是一个弱类型的语言,实际使用中,尤其对于ajax请求回来的数据最好做好类型检测.
原理:检测方法很多,比如判断一个对象是否是数组,可以判断这个对象有没有sort方法.但是这样的检测方法并不通用.我们可以借用Object.prototype.toString方法来判断,对于一个Array类型的对象,toString总是返回[object Array]
,对于一个String类型的对象,toString方法总是返回[object String]
.1
2
3
4
5
6
7
8
9
10
11
12
13//version1:
//这里不直接使用obj上的方法主要是obj上通常会有自己的toString方法,由于"属性屏蔽",
//在原型链上还没有找到Object上的toString方法就会停止.换用call方法可以避开原型链查找,
//直接调用方法Object上的toString方法
var isString = function(obj){
return Object.prototype.toString.call(obj) === '[object String]';
}
var isArray = function(obj){
return Object.prototype.toString.call(obj) === '[object Array]';
}
var isNumber = function(obj){
return Object.prototype.toString.call(obj) === '[object Number]';
}在version1版本中,我们三个isType函数,但是我们发现这三个函数长得还是非常类似的,这个过程完全可以抽象出来,下面我们将在version2版本中改进:
1
2
3
4
5
6
7
8
9
10
11//version2
//这里我们需要用到JS中的高阶函数的一点知识,函数可以作为返回值返回
//闭包
var isType = function(type){
return function(obj){
return Object.prototype.toString.call(obj) === '[object ' + type + ']';
}
}
var isString = isType('String');
var isArray = isType('Array');
var isNumber = isType('Number');在version2中的类型检测中,我们把类型检测的函数抽象了出来,构造三个类型检测函数.但是,构造三个类型检测的过程还是罗嗦,我们可以通过一个for循环来构造这些类型检测函数.
1
2
3
4
5
6
7
8
9
10//version3
var Type = {};
var typelist = ['String','Array','Number'];
for(var i = 0; i < typelist.length; i++){
(function(type){
Type['is'+type] = function(obj){
return Object.prototype.toString.call(obj) === '[object '+ type +']'
}
})(typelist[i]);
}javascript (浏览器端)JS的基本语法
a. 原生DOM操作
很多人刚开始接触前端的开发,就直接上手JQuery这种JS工具库,或者随着前端的热潮莫名其妙的就开始使用了所谓的框架,喊着MVC、MVVM的口号做着一些知其然不知其所以然
的开发.
这里重温下原生DOM操作,希望web开发追本溯源,不要被各种各样的轮子
冲晕了头.
很多人在刚接触web开发,都知道一个概念就是DOM
,DOM
将文档解析为一个由节点和对象(包含属性和方法的对象)组成的结构集合.说白了DOM就是沟通所谓的HTML字符串和JS的桥梁(提供document、window对象供JS操作).在HTML页面从html
节点到一个<!-- -->
注释节点都在DOM
中有其对应的位置.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16//配合对应的index.html
//手动添加元素
var div = document.createElement('div');
var p = document.createElement('p');
p.innerHTML = "hello world";
div.appendChild(p);
document.body.appendChild(div);
//手动载入script脚本
var script = document.createElement("script");
script.src = "http://cdn.bootcss.com/jquery/2.2.1/jquery.js";
document.body.appendChild(script);
//TASK手动载入图片
//应用场景,实际应用中图片并不是一次性加载完成的,而是根据当前的屏幕的滑动状态做的一个'lazy loading'
//tips 创建一个image元素,然后设置image元素的src属性b. 用模板来完成原生DOM操作
原生的DOM操作功能很强大,但是对于一些应用场景比较复杂的情况写起来比较繁琐,代码的可读性并不强,
//比如我们要做一个动态增加表格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<html>
<head>
<meta charset="UTF-8">
<title>addTable</title>
</head>
<body>
<table style="margin: 0 auto;width:500px;text-align: center;" id="dataTable" border="1px solid black">
<tbody>
<tr>
<th>id</th>
<th>name</th>
<th>age</th>
<th>height</th>
<th>weight</th>
</tr>
<tr>
<td>1</td>
<td>dingwenjiang</td>
<td>23</td>
<td>175</td>
<td>150</td>
</tr>
</tbody>
</table>
<button id="btnAddRow" style="position: absolute;right: 300px;">增加一行</button>
<script>
var table = document.getElementById("dataTable");
var tbody = table.childNodes[1];
var parser = new DOMParser();
document.getElementById("btnAddRow").onclick = (function(){
var counter = 2;
return function(){
var str = '<tr>'
+'<td>'+counter+'</td>'
+'<td>dingwenjiang</td>'
+'<td>23</td>'
+'<td>175</td>'
+'<td>150</td>'
+'</tr>';
tbody.innerHTML = tbody.innerHTML + str;
counter++;
}
})();
</script>
</body>
</html>这种情况下,直接拼接字符串,把值追加到
tbody
的innerHTML
中的复杂度我们还是可以忍受的,但是随着拼接的难度越来越大,尤其是碰到大量字符转义
的问题的时候,直接拼接的做法开发效率越来越低.
这个时候我们可以采用模板(这里指的是前端渲染,而不是freemarker、jsp这样的后端模板)的方式来完成. 这样我们用了一个叫做underscore
中提供的模板功能.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<html>
<head>
<meta charset="UTF-8">
<title>addTable</title>
</head>
<body>
<table style="margin: 0 auto;width:500px;text-align: center;" id="dataTable" border="1px solid black">
<tbody>
<tr>
<th>id</th>
<th>name</th>
<th>age</th>
<th>height</th>
<th>weight</th>
</tr>
<tr>
<td>1</td>
<td>dingwenjiang</td>
<td>23</td>
<td>175</td>
<td>150</td>
</tr>
</tbody>
</table>
<button id="btnAddRow" style="position: absolute;right: 300px;">增加一行</button>
<script>
var table = document.getElementById("dataTable");
var tbody = table.childNodes[1];
document.getElementById("btnAddRow").onclick = (function(){
var counter = 2;
return function(){
var str = '<tr>'
+'<td>'+counter+'</td>'
+'<td>dingwenjiang</td>'
+'<td>23</td>'
+'<td>175</td>'
+'<td>150</td>'
+'</tr>';
tbody.innerHTML = tbody.innerHTML + str;
counter++;
}
})();
</script>
</body>
</html>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16//改进后的click函数
//对于字符串的拼接部分,使用了模板的方式,只需要往里面传入一个对象,template方法就会生成对应的字符串
document.getElementById("btnAddRow").onclick = (function(){
var counter = 2;
var template = _.template('<tr>\
<td><%= counter %></td>\
<td>dingwenjiang</td>\
<td>23</td>\
<td>175</td>\
<td>150</td>\
</tr>');
return function(){
tbody.innerHTML = tbody.innerHTML + template({counter: counter});
counter++;
}
})();c. jQuery(write less,do more)
不管是原生DOM操作还是加上了模板的DOM操作,过程还是有点麻烦,用jq可以快速实现功能.1
2
3
4
5
6
7
8
9
10
11
12
13
14document.getElementById("btnAddRow").onclick = (function(){
var counter = 2;
var template = _.template('<tr>\
<td><%= counter %></td>\
<td>dingwenjiang</td>\
<td>23</td>\
<td>175</td>\
<td>150</td>\
</tr>');
return function(){
$(tbody).append(template({counter: counter}));
counter++;
}
})();JQuery本质上就是通过封装API,便捷了JS操作DOM.一个DOM元素转换为jQuery元素的转换规则为直接在DOM元素外面用括号包裹一个$;一个JQuery元素转换成DOM元素的规则为访问jQuery元素的第0个属性
1
2
3
4
5
6//比如
$('table')//是一个jQuery对象
$('table')[0]//是一个DOM对象
document.getElementById('dataTable')//是一个DOM对象
$(document.getElementById('dataTable'))//是一个jQuery对象JQuery类数组的表现形式:
d. 应用
前端开发中单页应用现在谈到很多,单页应用使得前后端分离、把服务器的渲染压力放在客户端、用户体验相对较好.下面我们来实现一个简单的单页应用.这个简单的单页应用大体结构如下:
下面我们用sea.js + ajax实现一个简单的单页应用.这里sea.js主要为了模块化.单页应用的layout如下所示:
在开始单页应用之前还需要提一下web项目中的包管理:bower(bower
是一个基于nodejs
的工具,需要预先安装nodejs
环境)
在web项目中使用bower
的好处在于:1.开发者可以快速、准确的找到需要的包,比如bootstrap3
需要1.9.1-2版本的jQuery
,单独去找依赖容易出问题,用bower
更加方便、准确2.别人在使用我们的项目的时候,git clone
项目后,执行bower install
,bower
会根据bower.json
的配置文件去自动下载指定版本的依赖项.
比如bootstrap3的bower.json
同时我们使用gulp配置了一个livereload
项目目录如下所示:
- 其他
zencoding/emmet
typewriter.js
promise优化异步回调
socket.io