JavaScript 已经是当今世界最流行的编程语言之一
它只能在Web浏览器里跑?
没有一门编程语言是完美的
+
加号被同时用作数字的加法,以及字符串的拼接with
语句;
分号自动插入它连 class
都没有!
// 条件语句
if (lilei.money >= 1000000 && lilei.house >= 1) {
lilei.marry(hanmeimei);
} else {
lilei.dieAlone();
}
// 循环语句
while (lilei.money < 1000000 && lilei.house === 0) {
lilei.writeCodes();
}
// 选择语句
switch (lilei.workHoursPerDay) {
case 0: case 1: /* ... */ case 7:
company.fire(lilei);
break;
case 8:
company.pay(lilei, 3000);
break;
case 9:
company.pay(lilei, 4000);
break;
/* ... */
case 24:
lilei.die();
}
type
与 value
关联,而不是与 variable
var foo = 1;
foo = "A string";
foo = [1, 2];
foo = new Date();
foo = function(){};
在运行时,将给定的字符串当作代码语句执行
<script>
var func = <?php echo json_encode($user_send['func']); ?>;
eval(func + "()");
function sayHello() {}
function sayGoodbye() {}
</script>
对象的属性和方法都可以在运行时,增加、修改、删除
var obj = {
prop: "value"
};
// 增加方法
obj.other = function(){};
// 修改属性
obj.prop = "new value";
// 删除属性
delete obj.prop;
var obj = {
foo: "value",
bar: function() {
return this.foo;
}
};
// 使用字符串访问对象的属性或方法
console.log(obj.foo === obj["foo"]);
// 遍历对象的属性和方法
for (var key in obj) {
console.log(key, obj[key]);
}
函数是 JavaScript 中的一等公民
var bind = function(func, target) {
return function() {
func.apply(target, arguments);
};
};
var utils = {
trim: function(str) {
return str.replace(/^\s+|\s+$/g, "");
}
};
函数的参数数量没有限制
可以通过参数名或者 arguments
对象来访问参数
(function(){
// `arguments` 拥有数组的外观,可以通过数字下标访问各个参数
var arg1 = arguments[0], // 1
arg2 = arguments[1]; // 2
// 但它并不是数组
console.log(arguments instanceof Array); // false
console.log(arguments.slice); // undefined
// 对 `arguments` 进行数组相关操作
var argElse = Array.prototype.slice.call(arguments, 2);
console.log(argElse); // [3, 4]
})(1, 2, 3, 4);
C语言中的 block scope
:
int function foo(void) {
int bar = 1;
{
int bar = 2;
}
return bar; // 1
}
JavaScript 中的 function scope
:
function foo() {
var bar = 1;
{
var bar = 2;
}
return bar; // 2
}
访问一个变量时,会从本地变量和参数开始,逐级向上遍历作用域直到 global scope
var scope = 0, zero = "global-scope";
(function(){
var scope = 1, one = "scope-1";
(function(){
var scope = 2, two = "scope-2";
(function(){
var scope = 3, three = "scope-3";
// scope-3 scope-2 scope-1 global-scope
console.log([three, two, one, zero].join(" "));
console.log(scope); // 3
})();
console.log(typeof three); // undefined
console.log(scope); // 2
})();
console.log(typeof two); // undefined
console.log(scope); // 1
})();
console.log(typeof one); // undefined
console.log(scope); // 0
在一个函数中,定义另一个函数,称为函数嵌套。函数的嵌套将形成一个闭包。
function bind(func, target) {
return function() {
func.apply(target, arguments);
};
}
var scope = 0, zero = "global-scope";
(function(){
var scope = 1, one = "scope-1";
(function(){
var scope = 2, two = "scope-2";
(function(){
var scope = 3, three = "scope-3";
// scope-3 scope-2 scope-1 global-scope
console.log([three, two, one, zero].join(" "));
console.log(scope); // 3
})();
console.log(typeof three); // undefined
console.log(scope); // 2
})();
console.log(typeof two); // undefined
console.log(scope); // 1
})();
console.log(typeof one); // undefined
console.log(scope); // 0
function bind(func, target) {
return function() {
func.apply(target, arguments);
};
}
function foo(bar) {
console.log(this.count += bar);
}
var counterA = { count: 0 };
var counterB = { count: 0 };
var increaseA = bind(foo, counterA);
var increaseB = bind(foo, counterB);
increaseA(1); // 1
console.log(counterA.count); // 1
console.log(counterB.count); // 0
increaseB(2); // 2
increaseB(3); // 5
console.log(counterA.count); // 1
console.log(counterB.count); // 5
JavaScript : 基于原型的面向对象
JavaScript 中没有 class
,function
即 class
function Person() { }
使用 new Func
将创建一个 Func
的实例
function Person() { }
var lilei = new Person();
var hanmeimei = new Person();
function
即 constructor
function Person() {
console.log("A person is born");
}
var lilei = new Person();
console.log(lilei.constructor === Person); // true
通过关键字 this
或构造器(函数)的 prototype
来设置类的属性和方法
function Person(gender) {
this.gender = gender || "unkown";
}
Person.prototype.sayHello = function() {
console.log("Hello world!");
};
var lilei = new Person("male");
console.log(lilei.gender);
lilei.sayHello();
通过将子类的 prototype
赋值为父类的一个实例,来实现继承
function Student() {
Person.call(this);
}
Student.prototype = new Person();
// Student.prototype = Object.create(Person.prototype);
Student.prototype.constructor = Student;
Student.prototype.sayHello = function() {
console.log("Hello school!");
};
Student.prototype.sayGoodbye = function() {
console.log("Goodbye school!");
};
var lilei = new Student();
lilei.sayHello();
lilei.sayGoodbye();
console.log(lilei instanceof Person); // true
console.log(lilei instanceof Student); // true
访问一个对象的属性时,会先从自身属性开始,向上追溯原型链,
直到找到这个属性,或者遇到 null 为止
// lilei --> student --> person --> Object.prototype --> null
var person = {
gender: "unkown",
sayHello: function() {
console.log("Hello world!");
},
walk: function() {
console.log("I am walking");
}
};
var student = Object.create(person);
student.sayHello = function() {
console.log("Hello school!");
};
student.study = function() {
console.log("I am studying!");
};
var lilei = Object.create(student);
lilei.gender = "male";
console.log(lilei.gender);
lilei.walk();
lilei.sayHello();
lilei.study();
console.log(person.gender);
delete lilei.gender;
console.log(lilei.gender);
console.log(person.gender);
// lilei --> student --> person --> Object.prototype --> null
console.log(student.isPrototypeOf(lilei)); // true
console.log(person.isPrototypeOf(student)); // true
console.log(Object.prototype.isPrototypeOf(person)); // true
console.log(null === Object.prototype.__proto__); // true
+
加号作为二元运算符时,它既是数学运算的加法,也是字符串的拼接
console.log( 1 + 2 ); // 3
console.log( "3" + "4" ); // "34"
// 2
console.log( 1 + "3" );
console.log( "3" + 1 );
// 3
console.log( 1 + null );
console.log( 1 + undefined );
console.log( 1 + NaN );
// 4
console.log( "3" + null );
console.log( "3" + undefined );
console.log( "3" + NaN );
// 5
console.log( 1 + {} );
console.log( 1 + [] );
// 6
console.log( "3" + {} );
console.log( "3" + [] );
+
加号如何决定一段代码中的 +
是数学运算还是字符串拼接?
a + b:
pa = ToPrimitive(a)
pb = ToPrimitive(b)
if (pa is string || pb is string)
return concat(ToString(pa), ToString(pb))
else
return add(ToNumber(pa), ToNumber(pb))
+
两端的操作数的原始值with
将一个语句块的上下文绑定为一个指定对象
with (document) {
write("foo");
getElemntById("bar").innerHTML = "foobar";
alert("Hello world!");
}
// document.write("foo");
// document.getElemntById("bar").innerHTML = "foobar";
// window.alert("Hello world!");
with
不推荐使用 with
语句
with (document) {
write("foo");
getElemntById("bar").innerHTML = "foobar";
alert("Hello world!");
}
// document.write("foo");
// document.getElemntById("bar").innerHTML = "foobar";
// window.alert("Hello world!");
;
分号自动插入在语句结束时,你不必手动输入分号,换行即可
function foo() {
var bar = "value"
return bar
}
// `{}` 包围的语句块的最后一个语句的分号也可省略
function bar() { return "foo" }
;
分号自动插入过于依赖分号自动插入,将带来一些潜在问题
function foo() {
return
{
bar: 1
}
}
function bar() {
var a, b, c, d, e
a = b + c
(d + e).toString()
// a = b + c(d + e).toString();
}
看看这段代码,我们将得到什么结果?
var foo = 1;
function bar() {
// 这个条件成立吗?
if (! foo) {
var foo = 10;
}
alert(foo);
}
bar();
那么这段代码呢?
var a = 1;
function b() {
a = 10;
return;
function a() {}
}
b();
alert(a);
变量、函数的声明将提升到当前函数主体的顶部,
而不管这个声明语句是否出现在了不可到达的地方
var foo = 1;
function bar() {
// 这个条件成立吗?
if (! foo) {
var foo = 10;
}
alert(foo);
}
bar();
var a = 1;
function b() {
a = 10;
return;
function a() {}
}
b();
alert(a);
var foo = 1;
function bar() {
var foo;
if (! foo) {
foo = 10;
}
alert(foo);
}
bar();
var a = 1;
function b() {
function a() {}
a = 10;
return;
}
b();
alert(a);
eval
eval
是 JavaScript 的动态特性之一
<script>
var func = <?php echo json_encode($user_send['func']); ?>;
eval(func + "()");
function sayHello() {}
function sayGoodbye() {}
</script>
eval
不推荐使用 eval
eval
中传递的字符串中大多包含用户输入数据,易受攻击eval
中的语句代码的可阅读性差eval
,使用等同的函数式编程,将更高效和安全var multiStr = "this is a multi-line string, \
and this is the second line. \
yes, the string ends here";
假设我们后期做一些维护
var multiStr = "this is a multi-line string, \
and this is the second line. \
now i want to insert a line right here, \
yes, the string ends here";
function foo() {
var type = "first";
// do some jobs
typo = "second";
// do some else
return type;
}
foo();
arguments.callee
递归函数
function factorial(n) {
return n <= 1 ? 1 : factorial(n - 1) * n;
}
[1, 2, 3, 4, 5].map(factorial);
arguments.callee
不想污染命名空间,匿名函数如何递归?
[1, 2, 3, 4, 5].map(function(n) {
return n <= 1 ? 1 : /* what goes here? */ (n - 1) * n;
});
arguments.callee
我们有 arguments.callee
[1, 2, 3, 4, 5].map(function(n) {
return n <= 1 ? 1 : arguments.callee(n - 1) * n;
});
arguments.callee
不推荐使用 arguments.callee
arguments.callee
的开销是昂贵的arguments.callee
命名函数表达式
[1, 2, 3, 4, 5].map(function factorial(n) {
return n <= 1 ? 1 : factorial(n - 1) * n;
});
function foo() {
"use strict";
// now foo is in strict mode
function bar() {
// in strict mode too
}
}
with
语句eval
中严格模式的代码,不会将新的变量引入到当前作用域arguments.callee
, func.caller
, func.arguments
implements
, interface
, package
, private
, ... strict mode
能力有限我们需要一款优秀的代码质量工具
来检测我们的 JavaScript 代码中的错误和潜在问题
JSHint 是由 JSLint 分叉(fork)而来的,是社区推动的
( JSLint 的作者是著名 JS 权威 Douglas Crockford )
JSHint 本身使用 JavaScript 开发,
因此既可以运行在浏览器中,也可以运行在 Node.js 平台
强制选项示例:
选项 | 说明 |
---|---|
curly |
循环和条件语句块,始终带上 `{ }` |
eqeqeq |
禁止使用 == 和 != ,而应使用 === 和 !== |
es3 |
使用 ECMAScript 3 规范,以兼容老浏览器 IE 6/7/8 |
forin |
要求在 forin 循环里使用 `hasOwnProperty()` 过滤属性 |
latedef |
禁止在一个变量声明前使用它 |
noarg |
禁止使用 arguments.caller 和 func.caller |
quotmark |
保证引号一致性 |
undef |
禁止使用明显未声明的变量 |
strict |
要求所有函数运行在 ECMAScript 5 的严格模式下 |
trailing |
禁止行尾空白 |
maxdepth |
控制代码块的最大嵌套深度 |
maxstatements |
控制每个函数的最大语句数 |
松散选项示例:
选项 | 说明 |
---|---|
asi |
允许分号缺失 |
boss |
允许在期望是比较运算的地方使用赋值表达式 |
evil |
允许使用 eval |
multistr |
允许多行字符串(但仍会提示将导致错误的行尾空白) |
QUnit 最初是著名JS库 jQuery 的一部分,
后来独立了出来,作为一个JS单元测试框架
test("traps of +", function() {
expect(14);
strictEqual(1 + 2, 3);
strictEqual("3" + "4", "34");
strictEqual(1 + "3", "13");
strictEqual("3" + 1, "31");
strictEqual(1 + null, 1);
ok(isNaN(1 + undefined));
ok(isNaN(1 + NaN));
strictEqual("3" + null, "3null");
strictEqual("3" + undefined, "3undefined");
strictEqual("3" + NaN, "3NaN");
strictEqual(1 + {}, "1[object Object]");
strictEqual(1 + [], "1");
strictEqual("3" + {}, "3[object Object]");
strictEqual("3" + [], "3");
});
"grunt" 的意思是 "打呼噜",其目标就是让开发者们一劳永逸
Grunt 运行在 Node.js 平台,Grunt 及其插件都通过 npm 安装和管理
npm install -g grunt-cli
package.json
和 Gruntfile.js
文件)
npm install
,安装依赖库grunt
package.json
:管理作为 npm 模块项目的配置Gruntfile.js
:配置、定义、载入 grunt 任务npm install <module> --save-dev
:安装一个模块并保存依赖配置到 package.json
Gruntfile.js
示例
var fs = require("fs");
function htmlspecialchars(str) {
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
}
function replaceJshintComments(str) {
return str.replace(/[ \t]*\/\*\s*(jshint|global)\s+[\s\w\,\:\-]+\*\/(?:\r?\n|\r)/, "");
}
module.exports = function(grunt) {
// 幻灯片名称列表
var slideList = [
"cover",
"current", /*"popular",*/ "misunderstood",
"feature", "structured", "dynamic", "functional", "scope", "closure",
"object-oriented", "class", "instance", "constructor", "property", "inheritance",
"traps", "quality", "test", "automation",
"thanks"
];
// 遍历获取幻灯片文件名
var slides = [];
slideList.forEach(function(name){
slides.push("slides/" + name + ".html");
});
slides.unshift("src/intro.html");
slides.push("src/outro.html");
// 遍历获取示例代码文件内容
var codes = {};
var codeDirs = ["codes", "bad-codes"];
codeDirs.forEach(function(dir){
var codeList = fs.readdirSync(dir);
codeList.forEach(function(name){
var key = name.replace(/\.[^\.]+/, "");
codes[key] = replaceJshintComments(htmlspecialchars(fs.readFileSync(dir + "/" + name).toString("utf8")));
});
});
codes.Gruntfile = htmlspecialchars(fs.readFileSync("Gruntfile.js").toString("utf8"));
// 配置
grunt.initConfig({
// pkg: grunt.file.readJSON("package.json"),
codes: codes,
concat: {
options: {
separator: "\n\n",
process: true
},
dist: {
// src: ["src/intro.html", "slides/cover.html", "src/outro.html"],
src: slides,
dest: "dist/index.html"
}
},
jshint: {
options: {
"jshintrc": ".jshintrc"
},
dist: ["Gruntfile.js", "dist/main.js", "codes/*.js"]
},
uglify: {
impress: {
src: "dist/impress.js",
dest: "dist/impress.min.js",
}
},
qunit: {
all: ["test/*.html"]
}
});
// 载入任务
grunt.loadNpmTasks("grunt-contrib-concat");
grunt.loadNpmTasks("grunt-contrib-jshint");
grunt.loadNpmTasks("grunt-contrib-uglify");
grunt.loadNpmTasks("grunt-contrib-qunit");
// 注册任务
grunt.registerTask("default", ["jshint", "concat:dist"]);
grunt.registerTask("impress", ["uglify:impress"]);
grunt.registerTask("test", ["qunit"]);
};
Grunt 使用 Node.js 实现
配合丰富的任务插件,Grunt可以将许多的开发任务自动化
谢谢!