chapter 2 JavaScript概览
介绍
js是基于原型,面向对象, 弱类型的的动态脚本语言。
Javascript基础
类型:
- 基本类型包括number, boolean, string, null 及 undefined
复杂类型包括array, function 及 object
1
2
3
4
5
6
7
8
9
10var a = 5;
var b = 5;
b = a;
a; // => 5
b; //=> 6
var a = ['hello', 'world', 1]; //array
var b = a;
b[0] = 'bye';
a[0]; //=> 'bye'
b[0]; //= >'bye'类型的困惑(在js中判断变量值的类型并非易事)
你可以用两种方式来创建字符串1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24var a = 'woot';
var b = new String('woot')
a + b; //=> 'woot woot'
//要对这两个变量使用typeof和instanceof操作符,things go fun.
typeof a; //'string'
typeof b; //'object'
a instanceof String; //false
b instanceof String; //true
//事实上, 这两个变量值绝对都是字符串
a.substr == b.substr; //true
a == b; //true
a === b; //false 考虑类型是否相同了
//考虑有此差异,建议通过直观方式进行定义,避免使用new
//特定值会被判定为false: null, undefined, ' ', 0
var a = 0;
if(a){
//will not execute
}
a == false; //true
a === false; //false
//值得注意的是:
typeof null == 'object'; //很不幸,结果为true
//数组也不例外
typeof [] == 'object'; // true;
- 函数
javascript 中函数最重要。它们都属于一等函数:可以作为引用存储在变量中,随后可以像其他对象一样,进行传递:1
2var a = function(){}
console.log(a); //将函数作为参数传递
Javascript 中所有的函数都可以进行命名。有一点很重要,就是要区分函数名和变量名。1
2
3var a = function a () {
'function' == typeof a; //true
};
THIS, FUNCTION#CALL, FUNCTION#APPLY
下面代码中函数被调用时, this的值是全局对象。在浏览器中,就是window对象。1
2
3
4function a() {
window == this; //true
};
a();
调用以下函数时,使用.call 和.apply 方法可以改变this值:1
2
3
4function a() {
this.a == 'b'; // true
}
a.call({a: 'b'});
call 和 apply 区别在于 call 接受参数列表, 而apply接受一个参数数组1
2
3
4
5
6function a(b, c){
b == 'first'; //true;
c == 'second'; //true
};
a.call({a: 'b'}, 'first', 'second');
a.apply({a: 'b'}, ['first', 'second']);
- 函数的参数数量
函数有一个很有意思的属性—参数数量, 该属性指明函数声明时可接受的参数数量。在javascript中,该属性名叫length。1
2var a = function(a, b, c);
a.length == 3; //true
尽管者在浏览器端很少使用, 但是, 他对我们非常重要, 因为一些流行的node框架通过此属性来根据不同的参数个数提供不同功能的。
- 闭包
在javascript中, 每次函数调用时, 新的作用域就会产生。
在某个作用域中定义的变量只能在该作用域或其内部的作用域(该作用域中定义的作用域)中才能访问到:1
2
3
4
5
6
7
8
9
10var a = 5;
function woot(){
a == 5; //false;
var a = 6;
function test(){
a == 6; //true
};
test();
};
woot();
自执行函数是一种机制, 通过者中机制声明和调用一个匿名函数, 能够达到仅定义一个新作用域的作用。1
2
3var a = 3;
(function(){var a = 5;})();
a == 3; //true;
自执行函数对声明私有变量很有用的, 这样可以让私有变量不被其他代码所访问。
- 类
javascript中没有class关键词, 类只能通过函数来定义:1
function Animal(){}
要给所有的Animal的实例定义函数, 可以通过prototype属性来完成:1
2
3Animal.prototype.eat = function(food){
//eat method
};
这里值得一提的时, 在prototype的函数内部, this并非像普通函数那样指向global对象, 而是指向通过该创建的实例对象:1
2
3
4
5
6
7
8function Animal(name) {
this.name = name;
}
Animal.prototype.getName(){
return this.name;
};
var animal = new Animal('tobi');
a.getName == 'tobi'; //true
- 继承
javascript有基于原型的继承的特定。通常,你可以通过以下方式来模拟类型继承。
定义一个要继承自Animal的构造器。1
2
3
4
5
6
7
8
9
10
11function Ferret(){};
//要定义继承链, 首先创建一个Animal对象, 然后将其赋值给ferret.prototype.
//实现继承
Ferret.prototype = new Animal();
//随后可以为子类定义属性和方法:
Ferret.prototype.type = 'domestic';
//还可以通过prototype来重写和调用父类函数
Ferret.prototype.eat = function(food){
Animal.prototype.eat.call(this, foot);//调用父类函数
//ferrte特有的逻辑写在这里
}
慕课网
1 | var animal = new Animal(); |
这项技术是同类方案中最好的, 不会改变instanceof操作符的结果。
它的不足: 声明继承的时候创建的对象总要进行初始化(Ferret.prototype=new Animal),这种方式不好。一种解决办法就是在构造器中添加判断条件:1
2
3
4
5function Animal(a){
if(false !== a) return;
//初始化
}
Ferret.prototype = new Animal(false);
另外一个办法就是在定义一个新的空的构造器, 并重写它的原型:1
2
3
4
5
6function Animal(){
//constructor stuff
};
function f() {};
f.prototype = Animal.prototype;
Ferret.prototype = new f;
v8 提供了更为简洁的解决方案
- TRY{} CATCH{}
try/catch 允许进行异常捕获。下述代码会抛出异常var a = 5;
a[]
当函数抛出错误时, 代码就停止执行:1
2
3
4function () {
throw new Error('hi');
console.log('hi'); //这里永远不会被执行到
}
若使用try/catch则可以进行错误处理, 并让代码继续执行下去:1
2
3
4
5
6
7
8
9function () {
var a = 5;
try {
a[];
}catch(e){
e instanceof Error; //true
}
console.log('you get here!')
}
v8中的javascript
-OBJECT#KEYS
要想获取下述对象的键值(a and c):1
var a = {a: 'b', c: 'd'};
通常会使用如下迭代方式:1
for(var i in a) {}
通过对键值进行迭代, 可以将它们收集到一个数组中。不过, 如果采用如下方式对prototype进行扩展:1
object.prototype.c = 'd';
为了避免迭代中获取c可以用hasOwnProperty来进行检查1
2
3for(var i in a){
if(a.hasOwnProperty(i)){}
}
在v8中, 要获取对象的自由键, 还有更简单的方法:1
2var a = {a: 'b', c: 'd'};
Object.keys(a);
- ARRAY#ISARRAY
对数组使用typeof操作符会返回object, 然而大部分情况下, 我们要检查数组是否真的是数组。1
2
3
4Array.isArray(new Array) //true
Array.isArray([]) //true
Array.isArray(null) //false
Array.isArray(arguments) //false
数组方法
1
2
3
4
5
6
7
8
9
10
11
12//要遍历数组, 可以使用forEach($.each(jquery中))
[1, 2, 3].forEach(function(v){
console.log(v);
})
//要过滤数组中的元素
[1, 2, 3].filter(function(v){
return v < 3;
});
//要改变数组中每个元素的值,可以使用map
[1, 2,3 ].map(function(v){
return v * 2;
}); //[2, 4, 6]字符串方法
要移除字符串首末的空格, 可以使用:1
' hello '.trim();
JSON
v8 提供了JSON.stringfy 和JSON.parse的方法来对JSON数据进行解码和编码1
2var obj = JSON.parse('{"a": "b"}');
obj.a == 'b'; //trueFUNCTION#BIND
.bind 允许改变this的引用1
2
3
4
5function a(){
this.hello == "world"; //true
};
var b = a.bind({hello, "world"});
b();FUNCTION#NAME
V8还支持非标准的函数属性名:1
2
3
4
5
6
7
8
9
10
11
12var a = function woot() {};
a.name == "woot"; //true
//该属性用于v8内部的堆栈追踪。当错误抛出时, v8会显示一个堆栈追踪的信息, 会告诉你是哪个函数用导致了错误的发生。
var woot = function() {throw new Error();};
woot();
//Error:
// at [object context]: 1:32
//v8无法为函数的引用指派名字。然而,如果对函数进行了命名,v8就能在堆栈追踪中将函数名显示出来:
var woot = function buggy(){throw new Error();}
woot();
//Error
//at buggy ([object context]: 1: 34)PROTO(继承)
“proto“使得定义继承链变得更加容易1
2
3
4
5function Animal() {}
function Ferret() {}
Ferret.prototype.__proto__ = Animal.prototype;
//这是一个非常有用的特性:
//免去如下工作: 1.借助中间构造器, 2, 借助OOP工具类库。存取器
你可以通过调用方法来定义属性, 访问属性就使用defineGetter, 设置属性就使用defineSetter.
比如, 为Date对象定义一个ago属性, 返回以自然语言描述的日期间隔。
很多时候, 特别在软件中, 想用自然语言来描述日期距离某个特定时间点的时间间隔。比如: “某事件发生在三秒前”, 这种表达,要远比“某件事情发生在x年x月x日”, 这种表达更容易理解。
1 | //基于John的prettyDate |
然后简单的访问ago属性即可
var a = new Date(‘09/18/1991’);
a.ago;
