js模式学习
Posted 微风星语
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了js模式学习相关的知识,希望对你有一定的参考价值。
在javascript有二个特性,
第一个特性是js可直接使用变量,甚至无需声明。如
function sum(x,y){
result = x + y; //result是全局变量。
console.log(result);
}
sum(1,2);
console.log(result); //3
如果使用var可以避免这个:
function sum(x,y){
var result = x + y;
console.log(result);
}
sum(1,2);
console.log(result); //报错,找不到这个变量
创建隐式全局变量的反模式是带有var声明的链式赋值
function foo() {
var a = b = 0;
}
foo();
// console.log(a); //a is not defined
console.log(b); //0,因为 var a = (b = 0 );此时b未经声明,表达式的返回值是0,在赋给var声明的局部变量a。
对链式赋值的所有变量都进行声明。
function foo() {
var a , b;
a = b =0; //这样均为局部变量。
}
foo();
变量释放时的副作用
- 使用var创建的全局变量(这类变量在函数外部创建)不能删除
- 不使用var创建的隐含全局变量(尽管它是在函数内部创建)可以删除
var global_var = 1;
global_novar = 2; //反模式
(function(){
global_fromfunc = 3; //反模式
}());
delete global_var; //false
delete global_novar; //true
delete global_fromfunc; //true
console.log(typeof global_var); //number
console.log(typeof global_novar); //undefined
console.log(typeof global_fromfunc); //undefined
在ES5 strict模式中,为没有声明的变量赋值会抛出错误(类似上述代码中的两种反模式)
访问全局对象
从内嵌函数的作用域访问
var global = (function(){
return this;
}());
单一var模式(Single var Patten)
只使用一个var在函数顶部进行变量声明是一种非常有用的模式。
function func() {
var a = 1,
b = 2,
sum = a + b,
myobject = {},
i,
j;
//函数体。。。
}
提升:凌散变量的问题
JavaScript允许在函数的任意地方声明多个变量,无论在哪里声明,效果都等同于在函数顶部进行声明。这就是所谓的提升。在JavaScript中,只要变量是在同一个范围(同一函数)里,就视为已经声明,哪怕是在变量声明就使用。
//反模式
myname = "global";//全局变量
function func() {
alert(myname);//未定义
var myname = "local";
alert(myname); //局部变量
}
func();
前面的代码片断运行结果和以下代码一样。
//反模式
myname = "global";//全局变量
function func() {
var myname; //等同于 -> var myname = undefined;
alert(myname);//未定义
myname = "local";
alert(myname); //局部
}
func();
for循环
这种模式的问题在于每次循环迭代时都要访问数据的长度。特别是当myarray不是数据,而是html容器对时。
//次优循环
for(var i = 0; i< myarray.length; i++) {
//对myarray[i]的操作
}
以下代码:在这种方式在,对长度的值值提取一次,但应用到整个循环中。
for(var i = 0, max = myarray.length; i< max; i++) {
//对myarray[i]的操作
}
for-in 循环
var man = {
hand: 2,
legs: 2,
heads: 1
};
if(typeof Object.prototype.clone === "undefined") {
Object.prototype.clone = function () {};
}
var i,
hasOwn = Object.prototype.hasOwnProperty;
// for-in循环
for(i in man) {
if(hasOwn.call(man, i )) { //过滤
console.log(i, ":", man[i]);
}
}
避免使用隐式类型转换
var zero = 0;
if (zero === false) {
// 因为zero是0,而不是false,所以代码未执行
}
// 反模式
if (zero == false) {
//改代码会被执行。
}
避免使用eval()
该函数会将任意字符串当做一个JavaScript代码来执行。当需要讨论的代码是预先就编写好了(不是在动态运行时决定),是没有理由需要使用eval()。如果是运行时动态生成的,则也有更好的方法代替eval()。
//反模式
var property = "name";
alert(eval("obj." + property));
// 推荐的方法
var property = "name";
alert(obj[property]);
使用parseInt()的数值约定
该函数的第二个函数是一个进制参数,通常可以忽略该参数,但最后不要这么做。
var month = "06",
year = "09";
month = parseInt(month,10);
year = parseInt(yaer,10);
在ECMAScript 3版本中,0开始字符串会被当做一个八进制。而在ECMAScript 6 版本发生了改变。
对象字面量语法
- 将对象包装在大括号中(左大括号 “{” 和右大括号 “}”)
- 对象中以逗号分隔属性和方法。
- 用冒号来分隔属性名称和属性的值
- 当给变量赋值时,请不要忘记右大括号 “}” 后的分号。
来自构造函数的对象
在下面的例子中展示了以两种等价的方法来创建两个相同的对象:
第一种方法-使用了字面量
var car = {goes:"far"};
//另一种方法-使用内置构造函数
//反模式
var car = new Object();
car.goes = "far";
自定义构造函数
下面是对Person构造函数的定义:
var Person = function (name) {
this.name = name;
this.say = function () {
return "I am" + this.name;
};
};
当new操作符调用构造函数的时候,函数内部会发生以下情况:
- 创建一个空对象并且this变量引用了该对象,同时还继承了该函数的原型。
- 属性和方法被加入到this引用的对象中。
-
新创建的对象有this所引用,并且最后隐式地返回this(如果没有显示地返回其他对)
以上情况看起就像在后台发生了如下事情:var Person = function (name) { // 使用对象字面量模式创建一个新对象 // var this = {}; // 向this添加属性和方法 this.name = name; this.say = function () { return "I am" + this.name; } // return this; }
在以上代码中,为了简单起见,将say()方法添加到this中。其造成的结果是在任何时候调用new Person()时都会在内存中创建一个新的函数。这种方法的效率显然非常低下。因为多个实例之间的say()方法实际上没有改变。更好的选择应该是将方法添加到Person类的原型中。
Person.prototype.say = function() { return "I am" + this.name; };
以上语句并不是真相的全部。因为空对象实际上并不空,它已经从Person的原型中继承了许多成员。因此,它更像是下面的语句。
``//var this = Object.create(Person.prototype);
自调用构造函数
为了解决前面模式的缺点,并使得原型属性可以在实例对象中使用,可以在构造函数中检查this是否为构造函数的一个实例,如果为否,构造函数可以再次调用自身,并且在这次调用中正确地使用new操作符:
function Waffle() {
if (!(this instanceof Waffle)) {
return new Waffle();
}
this.tastes = "yummy";
}
Waffle.prototype.wantAnother = true;
//测试调用
var first = new Waffle(),
second = Waffle();
console.log(first.tastes); // 输出"yummy"
console.log(second.tastes); // 输出"yummy"
console.log(first.wantAnother); // 输出true
console.log(second.wantAnother); // 输出true
数组字面量
在下面的例子中,可以相同的元素,并以两种不同的方法创建两个数组,即使用Array()构造函数和使用字面量模式。
// 具有三个元素的数组
// 反模式
var a= new Array("itsy","bitsy","spider");
// 完全相同的数组
var a = ["itsy","bitsy","spider"];
console.log(typeof a ); //输出"object",这是由于数组本身也是对象类型。
console.log(a.constructor === Array); // true
数组构造函数的特殊性
避开new Array() 的另一个理由是避免构造函数中可能产生的陷阱。
当想Array()构造函数传递单个数字时,它并不会成为第一个数组元素的值。相反,它却设定了数组的长度。
//具有一个元素的数组
var a = [3];
console.log(a.length); //1
console.log(a[0]); // 3
//具有三个元素的数组
var a = new Array(3);
console.log(a.length); // 3
console.log(typeof a[0]); //输出undefined
上面例子中,可能并非预期的效果,但是与new Array()传递一个整数相比,如果向构造函数传递一个浮点数,则情况变得更加糟糕。
//使用数组字面量
var a = [3.14];
console.log(a[0]); // 3.14
var a = new Array(3.14); //输出RangeError:invalid array length
console.log(typeof a);
为了避免在运行时创建动态数组可能产生的潜在错误,坚持使用数组字面量表示法。
即时函数
即时函数模式是一种可以支持在定义函数后立即执行该函数的语法。
(function(){
alert(‘wathc out‘);
}());
下面的替代语法也是很常见的,但JSLint偏好使用第一种语法:
(function(){
alert(‘wathc out‘);
})();
这种模式非常有用,因为它为初始化代码提供了一个作用域沙箱(sandbox)。
(function(){
var days = [‘Sun‘,‘Mon‘,‘Tus‘,‘Wed‘,‘Thu‘,‘Fri‘,‘Sat‘];
today = new Date();
msg = ‘Today is ‘+ days[today.getDay()] + ‘,‘ + today.getDate();
alert(msg);
}()); // 输出 "Today is Fri, 13"
如果上面这些代码没有包装到即时函数中,那么days,today和msg等变量将会成为全局变量,并遗留在初始化代码中。
即时函数的参数
也可以将参数传递到即时函数中,如下例子:
(function (who, when) {
console.log("I met " + who + " on " + when);
}("Joe Black", new Date()));
一般情况下,全局对象是以参数形式传递给即时函数的,以便在不使用Window:指定全局作用域限定的情况下可以在函数内部访问该函数,这样将使得代码在浏览器环境之外时具有更好的互操作性。
(function (who, when) {
// 通过 `global`访问全局变量
}(this);
即时函数的返回值:
正如任何其他函数一样,即时函数可以返回值,并且这些返回值也可以分配给变量:
var result = (function(){
return 2 + 2;
}());
另一种语法:
var result = (function(){
return 2 + 2;
})();
下面这个例子中,即时函数返回的值是一个函数,它将分配给变量getResult,并且将简单地返回res值,该值被预计算并存储在即时函数的闭包中:
var getResutl = (function() {
var res = 2 + 2;
return function() {
return res;
}
} ());
当定义对象属性时可以使用即时函数。如果需要定义一个在对象生成期内永远都不会改变的属性,但是在定义它之前需要执行一些工作以找出正确的值。
var o = {
message: (function() {
var who = "me",
what = "call";
return what + " " + who;
}()),
getMsg: function(0 {}
return this.message;
)
};
//用法
o.getMsg(); //输出call me
o.message; // 输出call me
初始化时分支
var utils = {
addListener : function (el, type, fn) {
if (typeof window.addEventListener === ‘function‘) {
el.addEventListener(type, fn, false);
} else if (typeof document.attachEvent === ‘function‘) { // IE
} else { // 更早版本的浏览器
el[‘on‘ + type] = fn;
}
},
removeListener: function (el, type, fn) {
}
};
此段代码的问题在于效率比较低下。每次在调用utils.addListener()或utils.removeListener()时,都将会重复地执行相同的检查。
当使用初始化分支的时候,可以在脚本初始化加载时一次性特侧出浏览器特征。此时,可以在整个页面生命期内重定义函数运行方式。下面是一个可以处理这个任务的例子。
//接口
var utils = {
addListener: null,
removeListener: null
};
//实现
if(typeof window.addEventListener === ‘function‘) {
utils.addListener =function (el, type, fn) {
el.addEventListener(type, fn, false);
};
utils.removeListener = function (el, type, fn) {
el.removeEventListener(type, fn, false);
};
} else if(typeof document.attachEvent === ‘function‘) { //IE
utils.addListener(‘on‘ + type, fn );
utils.removeListener(‘on‘ + type, fn );
} else {
utils.addListener = function (el, type, fn) {
el[‘on‘ + type] = fn;
};
utils.removeListener = function (el, type, fn) {
e[‘on‘ + type] = null;
};
}
函数属性
var myFunc = function() {
var cachekey = JSON.stringify(Array.prototype.slice.call(arguments)),result;
if (!myFunc.cache[cachekey]) {
result = {};
//开销很大的操作
myFunc.cache[cachekey] = result;
}
return myFunc.cache[cachekey];
};
// 缓存存储
myFunc.cache = {};
请注意在序列化过程中,对象的“标识”将会丢失。如果有两个不同的对象并且恰好都具有相同的属性,这两个对象将会共享同一个缓存条目。
配置对象
配置对象模式是一种提供更整洁的API的方法。
function addPerson(conf);
var conf = {
username: "batman",
first: "Bruce",
last: "Wayne"
};
addPerson(conf);
配置对象的优点在于:
- 不需要记住众多的参数以及其顺序。
- 可以安全忽略可选参数。
- 更加易于阅读和维护
- 更加易于添加和删除参数。
而配置对象的不利之处在于:
- 需要记住参数名称。
- 属性名称无法被压缩。
当函数创建DOM元素时,这种模式可能是非常有用的,例如,可以用在设置元素的CSS样式中,以为元素和样式可能具有大量可选特征和属性。
Curry化
函数应用
在一些纯粹的函数式编程语言中,函数并不描述被调用,而是描述为应用。
//定义函数
var sayHi = function(who) {
return "Hello" + (who ? ", " + who : "") + "!";
};
//调用函数
sayHi(); //输出Hello
sayHi(‘world‘); // 输出hello world
//应用函数
sayHi.apply(null, ["hello"]); // 输出Hello world!
部分应用
Curry化
//curry化的add()函数
// 接受部分参数列表
function add(x, y) {
var oldx = x, oldy = y;
if(typeof oldy === "undefined") {
return function (newy) {
return oldx + newy;
};
}
//完全应用
return x + y;
}
//测试
console.log(typeof add(5)); // 输出 "function"
console.log(add(3,4)); // 7
//创建并存储一个新函数
var add2000 = add(2000);
console.log(add2000(10)); // 输出2010
更精简的版本
//curry化的add()函数
//接受部分参数列表
function add(x, y) {
if(typeof y === "undefined") { //部分
return function (y) {
return x + y;
};
}
// 完全应用
return x + y;
}
下面是一个通用curry化函数的示例
function schonfinkelize(fn) {
var slice = Array.prototype.slice,
stored_args = slice.call(arguments,1);
return function() {
var new_args = slice.call(arguments),
args = stored_args.concat(new_args);
return fn.apply(null,args);
}
}
//普通函数
function add(x, y) {
return x + y;
}
//将一个函数curry化以获得一个新的函数
var newadd = schonfinkelize(add,5);
console.log(newadd(4)); // 输出9
console.log(schonfinkelize(add,6)(7)); // 输出13
转换函数schonfinkelize()并不局限于单个参数或者单步Curry化。
下面是更多示例:
//普通函数
function add(a, b, c, d, e) {
return a + b + c + d + e;
}
// 可运行于任意数量的参数
schonfinkelize(add, 1, 2, 3)(5, 5); // 16
//两步curry化
var addOne = schonfinkelize(add, 1);
addOne(10,10,10,10);
var addSix = schonfinkelize(addOne, 2, 3);
addSix(5, 5); //输出16
以上是关于js模式学习的主要内容,如果未能解决你的问题,请参考以下文章