Javascript高级技巧(一)

提醒:本文发布于 2870 天前,文章内容可能 因技术时效性过期 或 被重新修改,请谨慎参考。

TOC
  1. 1. 作用域安全的构造函数
  2. 2. 函数加载优化技巧
    1. 2.1. 解决方案:惰性载入(1)
    2. 2.2. 解决方案:惰性载入(2)
  3. 3. 函数绑定
    1. 3.1. 常见问题
    2. 3.2. 改进:
  4. 4. 函数curry化
  5. 5. 防篡改对象
    1. 5.1. 不可扩展对象:(第一个级别的保护)
    2. 5.2. 密封的对象(第二个级别的保护)
    3. 5.3. 冻结的对象(第三个级别的保护)

作用域安全的构造函数

这是常见的构造函数:

function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
}
var person = new Person("Nicholas", 29, "Software Engineer"); // new

假设:

var person1 = Person("test",22,"FE");

那么 这个新对象的属性将会被绑定在window对象上面, 如果当前环境存在 window.name的操作 那么可能就会造成错误.

解决方法:(域安全的构造函数)

先判断this是否为该构造函数的实例

function Person(name, age, job){
if (this instanceof Person){
this.name = name;
this.age = age;
this.job = job;
} else {
return new Person(name, age, job);
}
}
var person1 = Person("Nicholas", 29, "Software Engineer");
alert(window.name); //""
alert(person1.name); //"Nicholas"
var person2 = new Person("Shelby", 34, "Ergonomist");
alert(person2.name); //"Shelby"

缺点:

function Polygon(sides){
if (this instanceof Polygon) {
this.sides = sides;
this.getArea = function(){
return 0;
};
} else {
return new Polygon(sides);
}
}
function Rectangle(width, height){
Polygon.call(this, 2); // 因为这个this, 不属于Polygon的this
this.width = width;
this.height = height;
this.getArea = function(){
return this.width * this.height;
};
}
var rect = new Rectangle(5, 10);
alert(rect.sides); //undefined

改正方法: 利用原型链

function Polygon(sides){
if (this instanceof Polygon) {
this.sides = sides;
this.getArea = function(){
return 0;
};
} else {
return new Polygon(sides);
}
}
function Rectangle(width, height){
Polygon.call(this, 2); //构造函数本身扩展到这里
this.width = width;
this.height = height;
this.getArea = function(){
return this.width * this.height;
};
}
Rectangle.prototype = new Polygon();
var rect = new Rectangle(5, 10);
alert(rect.sides); //2

函数加载优化技巧

普通情况下调用函数:

function createXHR() {
if (typeof XMLHttpRequest != "undefined") {
return new XMLHttpRequest();
} else if (typeof ActiveXObject != "undefined") {
if (typeof arguments.callee.activeXString != "string") {
var versions = ["MSXML2.XMLHttp.6.0", "MSXML2.XMLHttp.3.0", "MSXML2.XMLHttp"], i, len;
for (i = 0, len = versions.length; i < len; i++) {
try {
new ActiveXObject(versions[i]);
arguments.callee.activeXString = versions[i];
break;
} catch (ex) {
//跳过
}
}
}
return new ActiveXObject(arguments.callee.activeXString);
} else {
throw new Error("No XHR object available.");
}
}

缺点:每一次判断和循环造成性能上的损失

解决方案:惰性载入(1)

执行代码的时候损失性能

function createXHR(){
if (typeof XMLHttpRequest != "undefined"){
createXHR = function(){
return new XMLHttpRequest();
};
} else if (typeof ActiveXObject != "undefined"){
createXHR = function(){
if (typeof arguments.callee.activeXString != "string"){
var versions = [
"MSXML2.XMLHttp.6.0",
"MSXML2.XMLHttp.3.0",
"MSXML2.XMLHttp"
], i, len;
for (i=0,len=versions.length; i < len; i++){
try {
new ActiveXObject(versions[i]);
arguments.callee.activeXString = versions[i];
break;
} catch (ex){
//skip
}
}
}
return new ActiveXObject(arguments.callee.activeXString);
};
} else {
createXHR = function(){
throw new Error("No XHR object available.");
};
}
return createXHR();
}

解决方案:惰性载入(2)

var createXHR = (function () {
if (typeof XMLHttpRequest != "undefined") {
return function () {
return new XMLHttpRequest();
};
} else if (typeof ActiveXObject != "undefined") {
return function () {
if (typeof arguments.callee.activeXString != "string") {
var versions = ["MSXML2.XMLHttp.6.0", "MSXML2.XMLHttp.3.0",
"MSXML2.XMLHttp"],
i, len;
for (i = 0, len = versions.length; i < len; i++) {
try {
new ActiveXObject(versions[i]);
arguments.callee.activeXString = versions[i];
break;
} catch (ex) {
//skip
}
}
}
return new ActiveXObject(arguments.callee.activeXString);
};
} else {
return function () {
throw new Error("No XHR object available.");
};
}
})();

函数绑定

常见问题

var handler = {
message: "Event handled",
handleClick: function(event){
alert(this.message);
}
};
var btn = document.getElementById("my-btn");
EventUtil.addHandler(btn, "click", handler.handleClick); // undefined, 出现了作用域问题

改进:

 var handler = {
message: "Event handled",
handleClick: function(event){
alert(this.message);
}
};
var btn = document.getElementById("my-btn");
EventUtil.addHandler(btn, "click", function(event){ // 使用了闭包 但是尽量少用闭包
handler.handleClick(event);
});

这个解决方案在 onclick 事件处理程序内使用了一个闭包直接调用 handler.handleClick()。

当然,这是特定于这段代码的解决方案。创建多个闭包可能会令代码变得难于理解和调试。因此,很多JavaScript 库实现了一个可以将函数绑定到指定环境的函数。这个函数一般都叫 bind()。一个简单的 bind()函数接受一个函数和一个环境,并返回一个在给定环境中调用给定函数的函数,并且将所有参数原封不动传递过去。

语法如下:

function bind(fn, context){
return function(){
return fn.apply(context, arguments);
};
}

这个函数似乎简单,但其功能是非常强大的。在 bind()中创建了一个闭包,闭包使用 apply()调用传入的函数,并给 apply()传递 context 对象和参数。注意这里使用的 arguments 对象是内部函数的,而非 bind()的。当调用返回的函数时,它会在给定环境中执行被传入的函数并给出所有参数。

bind()函数按如下方式使用:

var handler = {
message: "Event handled",
handleClick: function(event){
alert(this.message);
}
};
var btn = document.getElementById("my-btn");
EventUtil.addHandler(btn, "click", bind(handler.handleClick, handler));

ECMAS5: 存在bind方法, 但IE9及以上支持

var handler = {
message: "Event handled",
handleClick: function(event){
alert(this.message + ":" + event.type);
}
};
var btn = document.getElementById("my-btn");
EventUtil.addHandler(btn, "click", handler.handleClick.bind(handler));

函数curry化

函数柯里化(function currying) ,它用于创建已经设置好了一个或多个参数的函数。函数柯里化的基本方法和 函数绑定 是一样的:使用一个闭包返回一个函数。两者的区别在于,当函数被调用时,返回的函数还需要设置一些传入的参数。

function add(num1, num2){
return num1 + num2;
}
function curriedAdd(num2){ // 比如说说实现 计算 和 打印 功能的分开
return add(5, num2);
}
alert(add(2, 3)); //5
alert(curriedAdd(3)); //8

柯里化函数通常由以下步骤动态创建:调用另一个函数并为它传入要柯里化的函数和必要参数。下面是创建柯里化函数的通用方式。

function curry(fn){
var args = Array.prototype.slice.call(arguments, 1);
return function(){
var innerArgs = Array.prototype.slice.call(arguments);
var finalArgs = args.concat(innerArgs);
return fn.apply(null, finalArgs);
};
}

防篡改对象

不过请注意:一旦把对象定义为防篡改,就无法撤销了

不可扩展对象:(第一个级别的保护)

默认情况下,所有对象都是可以扩展的。也就是说,任何时候都可以向对象中添加属性和方法。

var person = { name: "Nicholas" };
person.age = 29;

即使第一行代码已经完整定义 person 对象,但第二行代码仍然能给它添加属性。现在,使用Object.preventExtensions()方法可以改变这个行为,让你不能再给对象添加属性和方法。

例如:

var person = { name: "Nicholas" };
Object.preventExtensions(person);
person.age = 29;
alert(person.age); // undefined,非严格模式下静默失败,严格模式下抛出错误

虽然不能给对象添加新成员,但已有的成员则丝毫不受影响。你仍然还可以修改和删除已有的成员。另外,使用 Object.istExtensible()方法还可以确定对象是否可以扩展。

var person = { name: "Nicholas" };
alert(Object.isExtensible(person)); //true
Object.preventExtensions(person);
alert(Object.isExtensible(person)); //false

密封的对象(第二个级别的保护)

var person = { name: "Nicholas" };
Object.seal(person);
person.age = 29;
alert(person.age); //undefined
delete person.name; // 无效 , 严格模式下抛出错误
alert(person.name); //"Nicholas"

var person = { name: "Nicholas" };
alert(Object.isExtensible(person)); //true
alert(Object.isSealed(person)); //false
Object.seal(person);
alert(Object.isExtensible(person)); //false
alert(Object.isSealed(person)); //true

冻结的对象(第三个级别的保护)

var person = { name: "Nicholas" };
Object.freeze(person);
person.age = 29;
alert(person.age); //undefined
delete person.name;
alert(person.name); //"Nicholas"
person.name = "Greg";
alert(person.name); //"Nicholas"

var person = { name: "Nicholas" };
alert(Object.isExtensible(person)); //true
alert(Object.isSealed(person)); //false
alert(Object.isFrozen(person)); //false
Object.freeze(person);
alert(Object.isExtensible(person)); //false
alert(Object.isSealed(person)); //true
alert(Object.isFrozen(person)); //true

访客评论