数据类型:
数字, 字符串, 布尔值, null, undefined, 对象[数组, 函数, 正则, 对象]
一. 对象篇
1.检索
使用[] 或 . 的访问属性
stooge['first-name'] 或者 stooge.first-name
1)如果访问一个不存在的属性,那么返回undefined,可以用||填充默认值
2)访问undefined的属性会抛出TypeError异常,可以使用&&屏蔽错误(或者if判断)
3)操作undefined的方法()会抛出TypeError异常,可以使用&&屏蔽错误(或者if判断)
如:stooge.secondname //undefined
var price = flight.price || "100"
var arrName = res.arrName || []
alert(alert(flight.price)); // undefined
alert(flight.price.cheap); // 抛出异常
// 解决方法
alert(flight.price && flight.price.cheap);
2.原型
每个对象都连接一个原型,可以继承原型中的属性
所有的通过对象字面量创建的对象都连接到Object.prototype这个标准的JS原型
当你创建对象时,可以选择某个对象作为它自己的原型
if (! typeof Object.beget != "function") {
Object.beget = function (o) {
var F = function () {};
F.prototype = o;
return new F();
}
}
var another_stooge = Object.beget(stooge);
alert(another_stooge['first-name']); // zhang
alert(another_stooge['last-name']); // fei
委托的过程:查找对象的属性 –> 查找原型对象的属性 –> 查找原型的属性 …–> Object.prototype中去
例如查找another_stooge的’first-name’的属性值,首先从该对象开始,然后去原型对象stooge中,找到了
如果没有找到,继续到原型中找,一直找到Object.prototype中,如果没有,则返回undefined
- 原型连接只有在检索值得时候才会被用到.
- 原型连接在更新时是不起作用,改变某个对象时,不会触及该对象的原型.
3.反射
检测对象有什么类型的属性很容易,用typeof
typeof stooge['first-name']; // string
typeof flight.number; // number
原型链中的任意一个属性都会返回一个值
typeof flight.constructor; // function
typeof flight.toString; // function
typeof Object.beget; // function
function Person () {
this.name = "zhang";
}
Person.prototype.getName = function () {
return this.name;
}
var p = new Person();
alert(typeof p.name); // string
alert(typeof p.getName); // function
例: 处理并丢弃值为函数的属性
方法一:处理类型为函数的属性
if (typeof obj[item] != function){
...
}
方法二:使用hasOwnProperty(), 如果是对象拥有的独有属性,它将返回true.否则返回false.它不会检查原型链.
<script>
var flight = {
"number":10,
"age":300
}
console.log(flight.hasOwnProperty('number')); //true
console.log(flight.hasOwnProperty('constructor')); //false
</script>
var p = new Person();
p.name = "li";
alert(p.hasOwnProperty("name")); // true
4.枚举
for in语句遍历对象的所有属性,包含函数或者原型中的你可能不关心的属性
for(var name in flight) {
if(typeof flight[name] !== "function") {
document.writeln(name + " = " + flight[name]);
}
}
例子:把一个对象转化为数组
var newarr = [];
for(name in classStooge){
newarr.push(classStooge[name]);
}
5.删除
delete删除对象中的属性,可能会让来自原型链的属性显示出来
delete another_stooge.first-name;
6.减少全局污染
将你的应用程序全部打包到一个唯一的全局变量中
var MYAPP = {};
MYAPP.stooge = {
"last-name" : "fei"
}
MYAPP.flight = {
airline : "BeiJing",
arrival : {
city : "ShangHai"
}
};
二. 函数篇
1.函数对象
函数: 用于代码复用,信息隐藏和组合调用.
- 原型
- 字面量对象,连接到Object.prototype上
- 函数对象,连接到Function.prototype上, => Object.prototype上
2)每个函数对象在创建时,隐藏两个属性,
- 即函数的上下文
- 实现函数行为的代码
3)函数就是对象,可以像值一样使用
- 可以保存在变量,对象,数组中
- 可以做参数传递,可以返回函数
- 可以拥有方法
- 不同之处.在于函数可以被调用.
每个函数在创建时都会携带一个prototype属性,它的值拥有一个constructor属性且值为该对象的函数
2.调用
4种调用模式,调用的函数除了接受实参外,还将接受this和arguments两个参数
1)方法调用模式
this --> 所属的对象
// 当函数被保存为一个对象的属性,称为方法调用
var myObject = {
value : 0,
increment : function (inc) {
alert(this); // [object Object],即myObject对象
this.value += typeof inc === "number" ? inc : 1;
}
}
myObject.increment();
2)函数调用模式
this --> 指向全局对象
// 函数并非一个对象的属性时,此时是函数调用
var value = 1;
myObject.one = function () {
var helper = function () {
alert(this.value); // 1,这个匿名函数中的this统统指向window
}
helper();
}
myObject.one();
解决方法: var that = this; 即把执行环境保存下来.
此时内部函数可以通过that变量,访问到this.
that --> 指向所属的对象
3)构造器调用模式
this --> 指向新对象
// 如果一个函数在其前面加new调用,那么将创建一个连接到本函数的prototype成员的新对象
var Que = function (string) {
this.name = string;
}
Que.prototype.getName = function () {
return this.name;
}
var q = new Que("no");
4)Apply调用模式
它接受两个参数:第一个是将被绑定this的值,第二个是一个数组
// apply函数构建一个数组并去其去调用函数
var array = [3, 4];
alert(add.apply(null, array)); // 7, this没有被强制绑定值,即为window
var nameObject = {
name : "zhangfei"
}
// 将this绑定到nameObject中,并且调用Que.prototype.getName方法
alert(Que.prototype.getName.apply(nameObject)); // zhangfei
3.参数
默认arguments参数,它是传入实参的集合
var addNoParam = function () {
var res = 0
for (var i = 0, len = arguments.length; i < len; i++) {
res += arguments[i];
}
return res;
}
alert(addNoParam(1, 3, 4, 5))
4.返回 return
函数总是会有一个返回值
- 没有指定,则返回undefined
- 当函数是构造器函数模式调用时,返回this,并非一个对象
5.异常
异常时干扰程序的正常流程的非正常事故,当事故被查出时,会抛出一个异常
var addHasError = function (a, b) {
if (typeof a !== "number" || typeof b !== "number") {
throw {
name : "TypeError",
message : "add needs number"
}
}
return a + b;
}
var try_it = function () {
try {
addHasError("two", "one");
} catch(ex) {
TypeError : add needs number
document.writeln(ex.name + " : " + ex.message);
}
}
try_it();
6.扩充类型的功能
JS允许给基本类型添加方法,这样所有的函数都可以调用
例1:给对象加方法
Function.prototype.method = function (name, fun) {
// 如果添加的方法不存在,才可以添加
if(!this.prototype[name]) {
this.prototype[name] = fun;
return this;
}
}
例2: 添加一个取整的方法
Number.method("integer", function () {
alert(this); // this代表调用给方法的数字,这里是- 10 / 3
return Math[this < 0 ? 'ceil' : 'floor'](this);
});
alert((- 10 / 3).integer()); // -3
例3:添加一个去除字符串末端空白的方法
String.method("trim", function () {
return this.replace(/^\s+|\s+$/g, '');
});
alert(" zhang shan ".trim());
8.递归
递归函数会直接或间接的调用自身的一种函数
一个递归函数调用自身去解决它的子问题
例1:汉诺塔
<div>
<ul name="good">
<p></p>
<span name="good"></span>
</ul>
<ul>
</ul>
</div>
<h1>
<h3 name="good"></h3>
</h1>
var hanoi = function (disc, src, aux, dst) {
if (disc > 0) {
arguments.callee(disc - 1, src, dst, aux);
document.writeln("Move disc " + disc + " form " + src + " to " + dst + "<br/>");
arguments.callee(disc - 1, aux, src, dst);
}
}
hanoi(3, "盘子A", "盘子B", "盘子C");
例2:调用walk_the_DOM函数,它从给定的节点开始,依照html的顺序访问该树的每个节点
var walk_the_DOM = function walk (node, func) {
func(node);
node = node.firstChild;
while(node) {
walk(node, func);
node = node.nextSibling;
}
}
9.作用域
作用域控制着参数和变量的可见性和生命周期
jS不支持块级作用域,即if、for、while等花括号中的变量,可以在外部访问
10.闭包
闭包的好处在于内部函数可以访问定义它的外部函数的参数和变量
且闭包的生命周期比它的外部函数要长
我们是将调用函数的结果,即返回的对象赋值给myObject对象
var myObject = function () {
var value = 0;
return {
increment : function (inc) {
value = typeof inc === "number" ? inc : 1;
},
getValue : function () {
return value;
}
};
}();
myObject.increment(10);
alert(myObject.getValue()); // 10
// 返回的对象访问私有属性
// 并不需要new实例化
var quo = function (status) {
return {
getStatus : function () {
return status;
}
}
}
// var q = quo("zhang");
// alert(q.getStatus()); // zhang
var fade = function (node) {
var level = 1;
var step = function () {
var hex = level.toString(16);
node.style.background = "#FFFF" + hex + hex;
if (level < 16) {
level++;
setTimeout(step, 100);
}
}
setTimeout(step, 100);
}
fade(document.body);
内部函数能够访问外部函数的变量并非复制
糟糕的例子
<div>
<ul name="good">
<p>111</p>
<span name="good">222</span>
</ul>
<ul>
3333
</ul>
</div>
<h1>
var ul = document.querySelectorAll("ul");
var add = function (nodes) {
for(var i = 0; i < nodes.length; i++) {
nodes[i].onclick = function() {
alert(i);
}
}
};
add(ul); // 你会发现,无论你点击哪一个ul都只会返回2
// 原因是闭包访问外部的变量是真实的并非复制的
// 像本例子中的i,它是不断发生变化的
// 改造后
var addClosed = function (nodes) {
for(var i = 0; i < nodes.length; i++) {
nodes[i].onclick = function(pos) {
return function () {
alert(pos);
}
}(i);
}
};
// addClosed(ul); // 这样就ok了
11.回调
函数让不连续的事件处理变得容易起来
var request = prepare_request(); // 请求预处理
send_request_asyc(request, function (response) {
display(response);
});
// 我们传递函数作为参数发送给send_request_asyc,当服务器响应后进行处理
同步发送的,如果服务器响应过慢,那么会出现客户端假死的现象
因此我们需要使用异步的方式,完成客户端与服务器端的交互
12.模块
模块是一个提供了接口但是隐藏了状态与实现函数的函数或者对象
我们可以用函数和闭包来构成模块
模块可以使我们完全摒弃全局变量的作用
Function.prototype.method = function (name, fun) {
// 如果添加的方法不存在,才可以添加
if(!this.prototype[name]) {
this.prototype[name] = fun;
return this;
}
}
String.method("deentityify", function () {
// 私有属性对象
var entity = {
quot : '"',
lt : "<",
gt : ">"
}
// 创建可以返回且可以访问entity私有变量的特权方法
return function () {
// 匹配以&开始,以;结束,且中间的字符不能为&或者;开头的字符
// this代表调用此函数的字符串
return this.replace(/&([^&;]+);/g, function (a, b) {
var r = entity[b];
// alert(a); // < " > &ddd;
// alert(alert(b));// lt undefined quot undefined gt undefined ddd
return typeof r === "string" ? r : a;
});
}
}());
document.writeln("<">&ddd;".deentityify()); // <">&ddd;
模块的一般形式: 一个定义了私有变量或者函数的函数, 利用闭包创建可以访问到私有变量或者函数的特权方法, 最后返回这个特权方法,或者将其存放到一个可以访问的地方。 接下来我们要构建一个可以生成序列化的模块
var serial_maker = function () {
// 定义两个私有的变量,前缀和序列号
var prex = '';
var seq = 0;
// 返回一个可以访问私有变量的对象
return {
setPrex : function (p) {
prex = String(p);
},
setSeq : function (s) {
seq = Number(s);
},
gensyn : function () {
var result = prex + seq;
seq++;
return result;
}
};
};
var maker = serial_maker();
maker.setPrex("U");
maker.setSeq(200);
alert(maker.gensyn()); // U200
maker.setPrex("Q");
alert(maker.gensyn()); // Q200
13.级联
一些方法,只是修改和设置的操作,没有返回值,默认为undefined,
我们可以让他们返回this,这样这个方法就开启了级联,即可以依次调用同一个对象的许多方法
getElement("div").move(200, 200).setBackground("#ccc").....; //这个div可以一次性完成许多工作
14.套用--柯里化
套用允许我们将函数与传递给它的参数结合产生新函数
var add = function (a, b) {
return a + b;
}
Function.method("curry", function () {
var slice = Array.prototype.slice;
// 取得外部参数,即下面实例的10
var args = slice.apply(arguments);
alert(args); // 10
var that = this;
alert(this); // 调用curry的函数,即a下面实例的add()方法
return function () {
alert(this); // [object Window]
return that.apply(null, args.concat(slice.apply(arguments)));
}
});
var add1 = add.curry(10);
alert(add1(100)); // 110
15.记忆
函数可以让对象记住先前操作的结果,这样就避免了无谓的计算
fibonacci函数被调用了453次,我们调用了11次,
var fibonacci = function (n) {
return n < 2 ? n : arguments.callee(n - 1) + arguments.callee(n - 2);
}
var start = Date.now();
for(var i = 0; i <= 30; i++) {
if (i == 30) {
document.writeln("// " + i + " : " + fibonacci(i) + "<br/>");
}
}
var end = Date.now();
document.writeln("原始使用时间: " + (end - start) + "<br/>"); // 用时1360
var memoizer = function (memo, fundamental) {
var shell = function (n) {
var result = memo[n];
if (typeof result !== 'number') {
result = fundamental(shell, n);
memo[n] = result;
}
return result;
}
return shell;
};
var fibonacci = memoizer([0, 1], function (shell, n) {
return shell(n - 1) + shell(n - 2);
});
alert(fibonacci(10)); // 55
alert(fibonacci(100)); // 354224848179262000000
var factorial = memoizer([1, 1], function (shell, n) {
return shell(n - 1) * n;
});
alert(factorial(5)); // 120
alert(factorial(50)); // 3.0414093201713376e+64
三. 继承篇
1.伪类 构造器继承
var Person = function (name) {
this.name = name;
}
Person.prototype.getName = function () {
return this.name;
}
Person.prototype.getSaying = function () {
return this.saying || "";
}
var p = new Person("zhang");
alert(p.getName()); // zhang
var Student = function (name) {
this.name = name;
this.saying = "student saying";
}
Student.prototype = new Person();
Student.prototype.purr = function (n) {
var i, s = '';
for(i = 0; i < n; i++) {
if(s) {
s += "-";
}
s += "r";
}
return s;
}
var s = new Student("li");
alert(s.getSaying()); // sayistudent
alert(s.purr(10)); // r-r-r-r-r-r-r-r-r-r
伪类模式的本意是向面向对象靠拢,但是看起来总是格格不入,我们可以优化一个
Function.prototype.method = function (name, func) {
if (! this.prototype[name]) {
this.prototype[name] = func;
}
return this;
}
Function.method("inherits", function (Parent) {
this.prototype = new Parent();
return this;
});
var Teacher = function (name) {
this.name = name;
this.saying = "teacher saying";
}
.inherits(Person)
.method("purr", function (n) {
var i, s = '';
for(i = 0; i < n; i++) {
if(s) {
s += "-";
}
s += "r";
}
return s;
})
.method("getName", function (){
return this.getSaying() + " " + this.name + this.getSaying();
});
var t = new Teacher("wang");
alert(t.getName()); // wang
alert(t.purr(3)); // r-r-r
缺陷
没有私有环境
所有的属性都是公开的
无法访问super父类的方法
在实例化时,如果忘记new,那么this会指向全局环境
2.对象说明符
有时候,实例化构造器时会有许多参数,我们会感觉很麻烦
var myObject = maker(f, l, s, c);
//如下,使用对象说明符
//这样我们就可以很清晰的看清楚了参数的顺序关系了
//对象说明符和JSON结合使用有意想不到的便捷
var myObject = maker({
first : f,
last : l,
state : s,
city : c
});
3.原型
让我们用对象字面量的形式创建一个有用的对象
var myPerson = {
name : "hello everyone",
getName : function() {
return this.name;
},
says : function () {
return this.saying || "";
}
}
Object.prototype.create = function (o) {
if(typeof Object.create !== "function") {
Object.create = function () {
var F = function () {};
F.prototype = o;
return new F();
}
}
}
var worker = Object.create(myPerson);
worker.name = "worker";
worker.saying = "saying";
worker.getSaying = function () {
return this.name + " " + this.saying;
}
alert(worker.getSaying()); // worker saying
alert(myPerson.getName()); // hello everyone
4.函数化
迄今为止,我们所有的继承的方式都无法保护隐私,我们可以用模式的方式来解决
var mammal = function (spec) {
var that = {};
that.getName = function () {
return spec.name;
};
that.getSaying = function () {
return spec.saying || "";
}
return that;
}
var m = mammal({name : "zhang"});
alert(m.name); // undefined
alert(m.getName()); // zhang
让cat继承mammal
Function.prototype.method = function (name, func) {
if(this.prototype[name] !== "function") {
this.prototype[name] = func;
}
return this;
}
var cat = function(spec) {
spec.saying = spec.saying || "";
var that = mammal(spec);
override getName method
that.getName = function () {
return spec.name + " : " + spec.saying;
};
that.purr = function (n) {
var i, s = '';
for(i = 0; i < n; i++) {
if(s) {
s += "-";
}
s += "r";
}
return s;
}
return that;
}
var c = cat({name : "wang", saying : "hello dear"});
alert(c.getName()); // wang : hello dear
alert(c.purr(3)); // r-r-r
// 调用父类的方法
Object.method("superior", function (name) {
var that = this,
method = that[name];
return function () {
return method.apply(that, arguments);
}
});
var littleCat = function (spec) {
var that = cat(spec);
get_super_name = that.superior("getName");
that.getName = function () {
return "like " + get_super_name() + " body";
}
return that;
}
var lc = littleCat({name : "zhangFei", saying : "Come On"});
alert(lc.getName()); // like zhangFei : Come On body
5.部件
var eventuality = function (that) {
var registry = {};
that.fire = function (event) {
// alert(event.type); // show
var array, func, handler, i,
type = typeof event === "string" ? event : event.type;
if(registry.hasOwnProperty(type)) {
array = registry[type];
// alert(array); // [object Object]
for(i = 0; i < array.length; i++) {
handler = array[i];
// alert(handler); // [object Object]
func = handler.method;
// alert(func()); // show
if (typeof func === 'string') {
func = this[func];
}
func.apply(this, handler.parameters || [event]);
}
}
return this;
}
that.on = function (type, method, parameters) {
var handler = {
method : method,
parameters : parameters
};
// handler = {
// method : "show",
// parameters : [1, 2, 3]
// }
if(registry.hasOwnProperty(type)) {
registry[type].push(handler);
} else {
registry[type] = [handler];
}
// alert(registry[type][0].method()); // show
// alert(registry[type][0].parameters); // 1, 2, 3
return this;
}
return that;
}
var e = eventuality({ name : "zhang"});
var show = function () {
return "show";
}
e.on("show", show, [1, 2, 3]);
e.fire({type : "show"});
四. 数组篇
1.删除
delete删除,数组的索引却没有变化
delete number[number.length - 1];
console.log(number); // Array [ "one", "tow", "three", "five", <1个空的>]
改进: 因此我们用另一种形式,splice方法
number.splice(number.length - 2, 2);
console.log(number); // Array [ "one", "tow", "three" ]
2.容易混淆的地方
例:判断一个变量是数组或对象
var is_array = function (value) {
return value &&
typeof value === "object" &&
typeof value.length === "number" &&
typeof value.splice === "function" &&
!(value.propertyIsEnumerable('length'));
}
console.log(is_array([1,2])); // true
3.方法
我们为Array.prototype可以扩展方法
Function.prototype.method = function (name, func) {
if (!this.prototype[name]) {
this.prototype[name] = func;
}
return this;
}
Array.method("reduce", function (func, value){
var i, len;
// this表示方法调用模式
// 代表调用该方法的数组对象
for (i = 0, len = this.length; i < len; i++) {
value = func(this[i], value);
}
return value;
});
var data = [1, 2, 3, 4, 5];
var add = function (a, b) {
return a + b;
}
var mult = function (a, b) {
return a * b;
}
console.log(data.reduce(add, 0)); // 15
console.log(data.reduce(mult, 1)); // 120
同时,我们也可以为数组添加方法,以证明数组就是对象
data.total = function () {
return this.reduce(add, 0);
}
console.log(data.total()); // 15
4.指定初始值
定义一个dim方法,让数组初始值为0
Array.method("dim", function (len, init) {
var i, len;
this.length = len;
for(i = 0; i < this.length; i++) {
this[i] = init;
};
return this;
});
console.log([].dim(10, 0)); // Array [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]