JavaScript版本
JavaScript的普及使得其于1997年正式成为国际标准,其官方名称为ECMAScript
1999年定稿第三版ECMAScript标准,简称ES3
2009年重大改进的标准为第5版本ECMAScript,即ES5
这么多不同的版本,但是并不支持程序员指定某个JavaScript的版本来执行代码,所以我们必须精心编写Web程序,使得其在所有的浏览器上始终工作如一。
举例来说明一下,ES5支持const关键字,但当将它部署在不识别const的浏览器上时就会出现语法错误
严格模式
严格模式允许你选择在受限制的JavaScript版本中禁止使用一些JavaScript语言中问题较多或易于出错的特性。
使用的方式有两种
1、在文件的开始使用"use strict";以保证整个文件启用严格模式
2、在函数顶部使用"use strict";以启用该函数的严格模式
具体浏览器下严格模式怎么用请见我的另外一篇博文ECMAScript严格模式简介
JSON
所谓json字符串,是指该字符串变量的值与json的格式相同,但是不是json对象。
将字符串转换为json对象使用函数eval,eval("(" + status_process+ ")");
结论:从后台传入到前台的是json字符串,不是真正的json对象,因此需要使用eval函数转换。
闭包
闭包的两个特点:
1、作为一个函数变量的一个引用 - 当函数返回时,其处于激活状态。
2、一个闭包就是当一个函数返回时,一个没有释放资源的栈区。
其实上面两点可以合成一点,就是闭包函数返回时,该函数内部变量处于激活状态,函数所在栈区依然保留.
闭包只能取得包含函数中任何变量的最后一个值。
在闭包中使用this也会导致问题。this对象是在运行时基于函数的执行环境绑定的。
JavaScript坑
1.Array的方法,concat\slice是返回副本不在原数组上操作的;reverse\sort\splice是直接在原数组上操作的;还有各个方法的返回值,各个方法的参数
2.通过for-in循环输出的属性名的顺序是不可预测的
3.javascript没有块级作用域,但可以用匿名立即执行函数模仿块级作用域
4.this的指向是在运行的时候确定的
5.函数的执行依赖于变量的作用域,这个作用域是在函数定义时决定的,而不是函数调用时
6.闭包中所记录的*变量,只是对变量的引用,而非变量的值,当这个变量改变了,闭包里获取的变量值也会改变
7.如果构造函数返回了一个对象,用new生成对象实例时,获取到的是返回的对象
8.若两个变量是对同一个对象的引用,改变了其中一个,另一个,你懂的
9.函数的名称仅仅是一个包含指针的变量而已
10.函数的length是指形参的个数,arguments.length是实际接收到的参数的个数
Prototype
对于每个构造函数来说,都有一个prototype属性。对于每个对象实例来说,都有_proto_属性。
function Person(){}
Person.prototype={//重写了Person.prototype
name:"Mike",
age:18,
sayName:function(){
alert(this.name);
}
}; var friend = new Person();
alert(friend instanceof Person);//true
alert(friend.constructor == Person);//false friend.constructor不再指向Person了
alert(friend.constructor == Object);//true
可以通过在Person.prototype中添加constructor:Person来避免,但是这也会导致constructor变为可列举的属性。
function Person() { }
var friend = new Person();
Person.prototype =
{
name: "Mike",
age: 18,
sayName: function () {
alert(this.name);
},
constructor: Person
};
friend.sayName();
调用friend.sayName()的结果是什么? 会报错。 为什么? 重写原型对象切断了现有原型与之前任何原型对象实例之间的联系。
尽量在重写prototype之后再声明对象才不会出现上面的那样错误。
原型对象中的引用类型的属性,对于每个实例来说也都是共享的。
单例模式
单例就是保证一个类只有一个实例。
单例模式要点:某个“类”只有一个模式,必须自己创建这个实例,且向系统提供这个实例。
var Singleton = (function () {
var instantiated;
function init() {
return {
publicMethod: function () {
console.log('hello world');
},
publicProperty: 'test'
};
}
return {
getInstance: function () {
if (!instantiated) {
instantiated = init();
}
return instantiated;
}
};
})();
Singleton.getInstance().publicMethod();
桥接模式
在系统中,某些类由于自身逻辑,具有两个或两个以上维度的变化,如何使得该类型可以沿多个方向变化,但又不引入额外的复杂度,这就是桥接模式要解决的问题。
定义:桥接模式(Bridge),将抽象部分与它的实现部分分离,使他们可以独立的变化。
意图:将抽象与实现解耦。
桥接模式,就是把给抽象与现实对象搭一座桥,让对象方法即联系在一起,又是独立变化的,让代码耦合性降低的一种设计模式。
addEvent(element, 'click', getDrinkById);
function getDrinkById(e) {
var id = this.id;
asyncRequest('GET', 'drink.uri?id=' + id, function(resp) {
// callback response
console.log('Requested drink:' + resp.responseText);
});
}
getDrinkById必须有上下文,才能取得id,因为使用了this.id来取id.然后接着实现下面的逻辑,耦合过紧密。
function getDrinkById(id,callback) {
asyncRequest('GET', 'drink.uri?id=' + id, function(resp) {
// callback response
callback(resp.responseText);
});
}
addEvent(element,'click',getDrinkByIdBridge);
function getDrinkByIdBridge(e){
getDrinkById(this.id,function(drink){
console.log('Requested drink: '+ drink);
});
}
从逻辑上分析,把id传给getDrinkById函数式合情理的,且响应结果总是通过一个回调函数返回。现在做的是针对接口而不是实现进行编程,用桥接模式把抽象隔离开来。
这样,明显代码模块话,各个部分代码功能明确,耦合性大大降低,将监听器方法抽取出来,成为一个单独的API函数,而且保证该API函数与节点本身没有必然的耦合,就可以独立的运行getDrinkById这个函数。
判断一个对象是否为数组
外观模式
也可译为门面模式。它为子系统中的一组接口提供一个一致的界面, Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。引入外观角色之后,使用者只需要直接与外观角色交互,使用者与子系统之间的复杂关系由外观角色来实现,从而降低了系统的耦合度。
作用:
- 简化复杂接口
- 解耦和,屏蔽使用者对子系统的直接访问
外观模式在javascript的应用主要可以分为两类,某块代码反复出现,比如函数a的调用基本都出现在函数b的调用之前,那么可以考虑考虑将这块代码 使用外观角色包装一下来优化结构。
还有一种就是对于一些浏览器不兼容的API,放置在外观内部进行判断,处理这些问题最好的方式便是将跨浏览器差异全部集中放置到一个外观模式实例中来提供一个对外接口。
适配器模式
适配器模式的主要作用是将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些对象(类)可以一起工作。
适配器模式的使用场景:
1.想要使用一个已经存在的对象,但其方法或属性不符合要求
2.想要创建一个可以复用的对象,可以与其他不相关对象协同工作
3.想使用已经存在的对象,但是不能对每一个都进行原型继承以匹配它的接口。对象适配器可以适配它的父对象接口方法或属性。
装饰者模式
JavaScript整理函数
function parseURL(url) {
var a = document.createElement('a');
a.href = url;
return {
source: url,
protocol: a.protocol.replace(':',''),
host: a.hostname,
port: a.port,
query: a.search,
params: (function(){
var ret = {},
seg = a.search.replace(/^\?/,'').split('&'),
len = seg.length, i = 0, s;
for (;i<len;i++) {
if (!seg[i]) { continue; }
s = seg[i].split('=');
ret[s[0]] = s[1];
}
return ret;
})(),
file: (a.pathname.match(/\/([^\/?#]+)$/i) || [,''])[1],
hash: a.hash.replace('#',''),
path: a.pathname.replace(/^([^\/])/,'/$1'),
relative: (a.href.match(/tps?:\/\/[^\/]+(.+)/) || [,''])[1],
segments: a.pathname.replace(/^\//,'').split('/')
};
}
function generateRandomAlphaNum(len) {
var rdmString = "";
for (; rdmString.length < len; rdmString += Math.random().toString(36).substr(2));
return rdmString.substr(0, len);
}
JavaScript浮点数
var a = (0.1 + 0.2) + 0.3;
var b = 0.1 + (0.2 + 0.3);
console.log(a);//0.6000000000000001
console.log(b);//0.6
在JavaScript中是没有整数的,所有数字都是双精度浮点数。
尽管JavaScript中缺少明显的整数类型,但是可以进行整数运算。
位算术运算符不会将操作数作为浮点数进行运算,而是会将其隐匿转换为32位整数后进行运算。
尽可能的采用整数值运算,因为整数在表示时不需要舍入。
上述代码最好用如下代码替换
var a = ((10 + 20) + 30)/100;
var b = (10 + (20 + 30))/100;
console.log(a);//0.6
console.log(b);//0.6
当心强制类型转换
var obj = {
toString: function () {
return "[object MyObject]";
},
valueOf: function () {
return 17;
}
};
console.log(obj.valueOf);
也许你会想当然的认为结果是17,可是结果却如下图所示,是不是让你大失所望。原因是对象通过valueOf方法强制转换为数字,通过toString方法强制转换为字符串。
像写如下代码,你本来的原意是想如果用户没有传入x,y,则设置默认值为320,240,结果当你x,y分别传入0,0的时候,函数也将x,y设置为了320,240
function point(x,y) {
if (!x) {
x = 320;
}
if (!y) {
y = 240;
}
return { x: x, y: y };
}
因为在JavaScript中有7个假值:false 、0、-0、""、 NaN、 null、 undefined,所以当你传入0的时候就自动转换为false了......
function point(x, y) {
if (typeof x==='undefined') {
x = 320;
}
if (typeof y==='undefined') {
y = 240;
}
return { x: x, y: y };
}
再来看一个例子。
"1.0e0" == {
valueOf: function () {
return true;
}
}//true
这也就是为什么在JS里面尽量使用全等运算符===而不要使用==,除非你了解如下规则。
尽量使用原始类型而不用封闭对象
var s = new String('hello');
console.log(typeof 'hello');//string
console.log(typeof s);//object
JavaScript有5个原始值类型:布尔值、数字、字符串、null和undefined
String对象是一个真正的对象,它不同于原始的字符串
var s1 = new String('hello');
var s2 = new String('hello');
console.log(s1 === s2);//false
console.log(s1 == s2);//false
由于String对象都是一个单独的对象,其总是只等于自身,对于非严格相等运算结果同样如此。
当做相等比较时,原始类型的封装对象与其原始值行为不一样。
命名函数相关问题
var f = function g() {
return 17;
} alert(g());
上述代码在ie6执行,弹出17,而在谷歌下则直接报错。这是因为JavaScript环境把f和g这两个函数作为不同的对象,从而导致不必要的内存分配。
所以上述代码在ie6下面最好写成如下所示
var f = function g() {
return 17;
}
var g = null;
alert(g());
看一下下面代码
function f() {
return 'global';
}
function test(x) {
function f() {
return 'local';
}
var result = [];
if (x) {
result.push(f());
}
result.push(f());
return result;
}
console.log(test(true));//[local,local]
console.log(test(false));//[local]
也许这样的代码你一眼就能看出答案,那下面的代码呢,你试着猜下结果。
function f() {
return 'global';
}
function test(x) { var result = [];
if (x) {
function f() {
return 'local';
}
result.push(f());
}
result.push(f());
return result;
}
alert(test(true));//[local,local]
alert(test(false));//[local]
ES5建议将非标准环境的函数声明转变成警告或错误,编写可移植的函数的最好方式是始终避免将函数声明置于局部块或子语句中。
Eval
eval最令人吐血的地方就是干扰作用域。也就是说eval函数具有访问调用它那时的整个作用域的能力。
var y = 'global';
function test(x) {
if (x) {
eval('var y="local";');
}
return y;
}
console.log(test(true));//local
console.log(test(false));//global
那怎样保证eval函数不影响外部作用域呢,那就是匿名函数立即执行。如下所示
var y = 'global';
function test(x) {
(function (src) {
eval(src);
})();
return y;
}
console.log(test('var y="local";'));//global
console.log(test('var z="local";'));//global
事实上,我们可以绑定eval函数到另一个变量名,通过该变量名调用函数会使代码失去对所有局部作用域的访问能力。
var y = "global";
function test(x) {
var x = "var y='local'";
var f = eval;
return f(x);
}
console.log(test());//undefnied
这个答案undefined我想应该是你想不到的吧。
将eval函数同一个字面量包裹在序列表达式中以达到强制使用间接调用eval函数的目的。其实我们最常用的间接调用eval的方式是如下所示
(0,eval)(src);
函数相关的巧用
var names = ['Fred', 'Wilma', 'Pebbles'];
var upper = [];
for (var i = 0, n = names.length; i < n; i++) {
upper[i] = names[i].toUpperCase();
}
console.log(upper);
像上面代码,我们要实现将数组中每个项都强制转换成大写,有什么更好的方面吗?
var names1 = ['Fred', 'Wilma', 'Pebbles'];
var upper1 = names1.map(function (name) {
return name.toUpperCase();
});
console.log(upper);
这个代码是否比上面要简单很多,事实上ES5提供了很多类似map的函数,如every,some,forEach等等
var aIndex = 'a'.charCodeAt(0);
var alphabet = '';
for (var i = 0; i < 26; i++) {
alphabet += String.fromCharCode(aIndex + i);
}
console.log(alphabet); var digits = '';
for (var i = 0; i < 10; i++) {
digits += i;
}
console.log(digits); var random = '';
for (var i = 0; i < 8; i++) {
random += String.fromCharCode(Math.floor(Math.random() * 26) + aIndex);
}
console.log(random);
像上面三个方面的逻辑当中都有类似的部分,大家都知道程序的坏味道就是重复相同的代码,那怎样让代码更加简单。将相似的逻辑封装成方法,然后。。。
function buildString(n, callback) {
var result = '';
for (var i = 0; i < n; i++) {
result += callback(i);
}
return result;
}
var alphabet1 = buildString(26, function (i) {
return String.fromCharCode(aIndex + i);
}); console.log(alphabet1); var digits1 = buildString(10, function (i) {
return i;
})
console.log(digits1); var random1 = buildString(8, function () {
return String.fromCharCode(Math.floor(Math.random() * 26) + aIndex);
})
console.log(random1);
是不是简单了很多。
arguments的一些异样。。。
先从例子说起吧,下面的方法原意是想得到两数相加的结果,可是却报错了
function callMethod(obj,method) {
var shift = [].shift;
shift.call(arguments);
shift.call(arguments);
return obj[method].apply(obj, arguments);
} var obj = {
add: function (x, y) {
return x + y;
}
};
console.log(callMethod(obj, 'add', 17, 25));
那么我们应该如何修改才能达到相加的效果呢?事实上很简单
function callMethod(obj, method) {
var args = [].slice.call(arguments, 2);
return obj[method].apply(obj, args);
}
var obj = {
add: function (x, y) {
return x + y;
}
};
console.log(callMethod(obj, 'add', 17, 25));//
这个例子说明了什么呢,也就是说我们不要随意修改arguments的值。[].slice.call(arguments)将arguments对象复制到一个真正的数组中再进行修改。
使用事实上在严格模式下,函数参数不支持对arguments修改。
function strict(x) {
'use strict';
arguments[0] === 'modified';
return x === arguments[0];
} function nonstrict(x) {
arguments[0] === 'modified';
return x === arguments[0];
}
console.log(strict('unmodified'))//true
console.log(nonstrict('unmodified'));//true
再看一个相关的例子
function values() {
var i = 0, n = arguments.length; return {
hasNext: function () {
return i < n;
},
next: function () {
if (i >= n) {
throw new Error('end of iteration');
}
return arguments[i++];
}
};
}
var it = values(1, 3, 4, 5, 2, 4, 6, 7, 8, 3, 45);
console.log(it.next());
console.log(it.next());
console.log(it.next());
console.log(it.next());
本来我们期望是得到1,3,4,5,结果却发现每个值都是undefined,原因在next函数中的arguments和values函数中的arguments不是一个对象,所以一定要当心函数嵌套层级问题,
那我们应该如何改正问题呢
function values() {
var i = 0, n = arguments.length, a = arguments; return {
hasNext: function () {
return i < n;
},
next: function () {
if (i >= n) {
throw new Error('end of iteration');
}
return a[i++];
}
};
}
var it = values(1, 3, 4, 5, 2, 4, 6, 7, 8, 3, 45);
console.log(it.next());
console.log(it.next());
console.log(it.next());
console.log(it.next());
也就是说我们最好使用临时变量来保存中间值。
当心函数的接收者
var buffer = {
entries: [],
add: function (s) {
this.entries.push(s);
},
concat: function () {
return this.entries.join('');
}
}; var source = ['897', '_', '8579'];
console.log(source.forEach(buffer.add));//这种是错误的 也许你期望着能得到897_8579 却发现报错了
事实上这个问题就是this导致的,也就是说在提取一个方法的时候不会将方法的接收者绑定到该方法的对象上。那如何解决呢,有好些方法呢,看看吧!
var buffer = {
entries: [],
add: function (s) {
this.entries.push(s);
},
concat: function () {
return this.entries.join('');
}
}; var source = ['897', '_', '8579'];
//console.log(source.forEach(buffer.add));//这种是错误的 也许你期望着能得到897_8579 却发现报错了
source.forEach(buffer.add, buffer);//这个方法就是说我们把this对象的接收者给传进去
source.forEach(function (s) {//也可以这样 在函数方法里面在适应的接收者上调用该方法...
buffer.add(s);
}); source.forEach(buffer.add.bind(buffer));//也可以使用bind方法来指定对象的接收者
console.log(buffer.add === buffer.add.bind(buffer));//再来看看这句代码 返回true 我相信你应该悟出了点什么吧
使用闭包而不是字符串来封装代码
function repeat(n, action) {
for (var i = 0; i < n; i++) {
eval(action);
}
}
function f() {
var a = 0, b = 1, n = 100, sum = 0;
for (var i = 0; i < n; i++) {
sum = a + b;
a = b;
b = a + b;
}
}
var start = [],
end = [],
timings = [];
repeat(1000, "start.push(Date.now());f();end.push(Date.now());");
上面的代码就是实现计时的功能。你或许不知道我在说什么,但是你接着往下看。
/*
*@desc:这样写就报错了,大家知道原因吗???
*/function benchmark() {
var start = [],
end = [],
timings = [];
repeat(1000, "start.push(Date.now());f();end.push(Date.now());"); for (var i = 0, n = start.length; i < n; i++) {
timings[i] = end[i] - start[i]; }
return timings[i];
}
上述代码报错了,你知道为什么我仅仅移动了一下位置,只是把代码移动到一个函数中就导致报错的原因了吗?因为这时的start只是benchmark函数内的局部变量,而eval执行时是调用的全局变量start.
那怎么样让代码正常执行不报错而又能起到封装效果呢?用下面的代码试试吧!
/*
*@desc:还是这样写比较靠谱
*/
function benchmark() {
var start = [],
end = [],
timings = [];
repeat(1000, function () {
start.push(Date.now()); f(); end.push(Date.now());
}); for (var i = 0, n = start.length; i < n; i++) {
timings[i] = end[i] - start[i]; }
return timings;
}
console.log(benchmark());;