( 译、持续更新 ) JavaScript 上分小技巧(二)

考虑到文章过长,不便于阅读,这里分出第二篇,如有后续,每15个知识点分为一篇...  

第一篇地址:( 译、持续更新 ) JavaScript 上分小技巧(一)

第三篇地址:( 译、持续更新 ) JavaScript 上分小技巧(三)

第四篇地址:( 译、持续更新 ) JavaScript 上分小技巧(四)

#30 - 将true的/false的值转换成boolean类型
你可以通过!!操作将为true/为false的值转换为boolean类型。

!!"" // false
!!0 // false
!!null // false
!!undefined // false
!!NaN // false

!!"hello" // true 字符串长度不为0时
!!1 // true
!!{} // true
!![] // true

          ### 2016-01-31 更新 ###          

#29 - 使用缓存的记忆让递归函数加速运行
斐波那切数列(Fibonacci sequence)想必大家都不陌生(针对学霸而言,在这之前本兽完全不知道这是个什么鬼,虽然经常会用到递归),我们可以在20秒内写出以下的函数:

var fibonacci = function(n){
    return n < 2 ? n : fibonacci(n-1) + fibonacci(n-2);
}

它确实是运行了,但是效率并不高。它做了大量的重复计算的工作,我们可以通过缓存先前计算结果的方法来加快速度。

var fibonacci = (function(){
    var cache = {
        0: 0,
        1: 1
    };
    return function(n){
        return n <= 1 ? cache[n] : (cache[n] = cache[n-1] + cache[n-2]);
    }
})()

同时,我们可以定义一个高阶函数,该高阶函数将接收一个函数作为参数并且该参数函数会返回一个已记忆的缓存版本。

var memoize = function(func){
    var cache = {};
    return function(){
        var key = Array.prototype.slice.call(arguments).toString();
        return key in cache ? cache[key] : (cache[key] = func.apply(this,arguments));
    }
}
fibonacci = memoize(fibonacci);

我们可以在很多情况下使用memoize()
GCD(最大公约数)

var gcd = memoize(function(a,b){
    var t;
    if (a < b) t=b, b=a, a=t;
    while(b != 0) t=b, b = a%b, a=t;
    return a;
})
gcd(27,183); //=> 3

阶乘计算

var factorial = memoize(function(n) {
    return (n <= 1) ? 1 : n * factorial(n-1);
})
factorial(5); //=> 120

#28 - 柯里化(局部套用)和局部应用
柯里化(局部套用)
柯里化将以下函数

f:X*Y_-R

转换为以下形式的函数:

f':X_>(Y->R)

我们仅调用f'配合第一个参数,来代替配合两个参数来调用f。返回的结果是一个我们配合第二个参数调用的函数并且返回结果值的函数。
因此,如果我们调用未柯里化的函数f:

f(3,5)

然后调用柯里化的函数f':

f(3)(5)

案例:
未柯里化的add()

function add(x, y) {
  return x + y;
}
add(3, 5);   // _> 8

柯里化的add()

function addC(x) {
  return function (y) {
    return x + y;
  }
}
addC(3)(5);   // _> 8

柯里化的运算法则:
柯里化取一个二元函数,返回一个包含一元函数的一元函数。
柯里化: 

(X × Y → R) → (X → (Y → R))

javascript 代码:

function curry(f) {
  return function(x) {
    return function(y) {
      return f(x, y);
    }
  }
}

局部应用:
局部应用将以下函数

f:X*Y_-R

给定一个固定的值,传入一个参数,以此产生个新函数:

f':Y->R

f'和f不一样,但是他只需要填入第二个参数,这就是为什么f'比f的参数数量少一个。
案例:
绑定函数的第一个参数通过函数plus5进行与加5:

function plus5(y) {
  return 5 + y;
}
plus5(3);  // _> 8

局部应用的运算法则:
局部调用取一个二元函数和一个值,并且产生一个一元函数。
局部: ((X × Y → R) × X) → (Y → R)
javascript 代码:

function partApply(f, x) {
  return function(y) {
    return f(x, y);
  }
}

          ### 2016-01-30 更新 ###          

#27 - 简短语句
Short-circuit evaluation指出,仅当第一个参数的论据不足以确定表达式的值的时候才执行第二个参数:当第一个参数的AND(&&)函数返回false,整体value也将是false;当第一个参数的OR(||)为true,则整体的值也是true。
以下是test情况和isTrue函数和isFalse函数:

var test = true;
var isTrue = function(){
  console.log('Test is true.');
};
var isFalse = function(){
  console.log('Test is false.');
};

使用AND(&&)逻辑:

// 正常语句.
if(test){
  isTrue();    // _> true
}
// 以上的功能使用 '&&' 完成
( test && isTrue() );  // _> true

使用OR(||)逻辑:

test = false;
if(!test){
  isFalse();    // _> false.
}
( test || isFalse());  // _> false.

逻辑OR(||)语句同时可以用来给函数的参数设置默认值:

function theSameOldFoo(name){ 
    name = name || 'Bar' ;
    console.log("My best friend's name is " + name);
}
theSameOldFoo();  // My best friend's name is Bar
theSameOldFoo('Bhaskar');  // My best friend's name is Bhaskar

逻辑AND(&&)语句同时可以用来避免使用undefined的属性的例外,案例:

var dog = { 
  bark: function(){
     console.log('Woof Woof');
   }
};
// 调用 dog.bark();
dog.bark(); // Woof Woof.
//但是如果bog是undefined, dog.bark() 会抛出个错误 "Cannot read property 'bark' of undefined."
// 我们可以使用 && 来避免这种情况.
dog&&dog.bark();   // 这样的话,只有在bog存在的时候才会调用 dog.bark()

         ### 2016-01-28 更新 ###          

#26 - 字符串列表的过滤和排序
你可能会有一个很大的字符串列表,你需要去对这个数组进行过滤和按字母排序。
在我们的例子中,我们将使用JavaScript来保存一系列的不同语言的关键字,但正如你所见,他们之间有重复,也没按字母顺序进行排序。所以这是一个完美的字符串列表(数组)来测试这个JavaScript小技巧。

var keywords = ['do', 'if', 'in', 'for', 'new', 'try', 'var', 'case', 'else', 'enum', 'null', 'this', 'true', 'void', 'with', 'break', 'catch', 'class', 'const', 'false', 'super', 'throw', 'while', 'delete', 'export', 'import', 'return', 'switch', 'typeof', 'default', 'extends', 'finally', 'continue', 'debugger', 'function', 'do', 'if', 'in', 'for', 'int', 'new', 'try', 'var', 'byte', 'case', 'char', 'else', 'enum', 'goto', 'long', 'null', 'this', 'true', 'void', 'with', 'break', 'catch', 'class', 'const', 'false', 'final', 'float', 'short', 'super', 'throw', 'while', 'delete', 'double', 'export', 'import', 'native', 'public', 'return', 'static', 'switch', 'throws', 'typeof', 'boolean', 'default', 'extends', 'finally', 'package', 'private', 'abstract', 'continue', 'debugger', 'function', 'volatile', 'interface', 'protected', 'transient', 'implements', 'instanceof', 'synchronized', 'do', 'if', 'in', 'for', 'let', 'new', 'try', 'var', 'case', 'else', 'enum', 'eval', 'null', 'this', 'true', 'void', 'with', 'break', 'catch', 'class', 'const', 'false', 'super', 'throw', 'while', 'yield', 'delete', 'export', 'import', 'public', 'return', 'static', 'switch', 'typeof', 'default', 'extends', 'finally', 'package', 'private', 'continue', 'debugger', 'function', 'arguments', 'interface', 'protected', 'implements', 'instanceof', 'do', 'if', 'in', 'for', 'let', 'new', 'try', 'var', 'case', 'else', 'enum', 'eval', 'null', 'this', 'true', 'void', 'with', 'await', 'break', 'catch', 'class', 'const', 'false', 'super', 'throw', 'while', 'yield', 'delete', 'export', 'import', 'public', 'return', 'static', 'switch', 'typeof', 'default', 'extends', 'finally', 'package', 'private', 'continue', 'debugger', 'function', 'arguments', 'interface', 'protected', 'implements', 'instanceof'];

因为我们不打算改变原始列表,我们将使用一个名为filter的高阶函数,它会基于我们传递给它的函数进行过滤并且返回一个经过过滤的新数组。判断语句会将当前关键字的索引与新列表中的索引进行比较,如果索引能匹配上,则会将这项推到新的数组中。
最后,我们要使用排序功能以一个比较函数作为唯一的参数来对过滤后的列表进行排序,返回一个按字母顺序排序的列表。

var filteredAndSortedKeywords = keywords
  .filter(function (keyword, index) {
      return keywords.indexOf(keyword) === index;
    })
  .sort(function (a, b) {
      if (a < b) return -1;
      else if (a > b) return 1;
      return 0;
    });

ES6(ES 2015)使用arrow函数将会看上去简单些:

const filteredAndSortedKeywords = keywords
  .filter((keyword, index) => keywords.indexOf(keyword) === index)
  .sort((a, b) => {
      if (a < b) return -1;
      else if (a > b) return 1;
      return 0;
    });

这是进行过滤和排序后的关键字列表:

console.log(filteredAndSortedKeywords);
// ['abstract', 'arguments', 'await', 'boolean', 'break', 'byte', 'case', 'catch', 'char', 'class', 'const', 'continue', 'debugger', 'default', 'delete', 'do', 'double', 'else', 'enum', 'eval', 'export', 'extends', 'false', 'final', 'finally', 'float', 'for', 'function', 'goto', 'if', 'implements', 'import', 'in', 'instanceof', 'int', 'interface', 'let', 'long', 'native', 'new', 'null', 'package', 'private', 'protected', 'public', 'return', 'short', 'static', 'super', 'switch', 'synchronized', 'this', 'throw', 'throws', 'transient', 'true', 'try', 'typeof', 'var', 'void', 'volatile', 'while', 'with', 'yield']

          ### 2016-01-27 更新 ###             

#25 - 使用立即执行函数表达式

被称为"Iffy"(IIFE-立即执行函数表达式)是一个在JavaScript中能够立即调用的匿名函数,并且还挺实用。

(function() {
// Do something
})()

用括号将匿名函数包裹起来,会将函数匿名函数转化成一个函数表达式或者变量表达式。因此,我们用一个未命名的函数表达式代替全局作用域下(或其他任何地方)的简单的匿名函数。

类似的,我们也可以为他创建一个名称,立即执行函数:

(someNamedFunction = function(msg) {
    console.log(msg || "Nothing for today !!")
}) (); // 输出 -> Nothing for today !!
someNamedFunction("Javascript rocks !!"); // 输出 -> Javascript rocks !!
someNamedFunction(); // 输出 -> Nothing for today !!

更多的信息,点击以下的链接 link1 link2
演示:jsPerf

          ### 2016-01-26 更新 ###          

#24 - 使用 === 代替 ==
==(或者!=)做对比的时候会将进行对比的两者转换到同一类型再比较。===(或者!==)则不会,他会将进行对比的两者做类型对比和值对比,相对于 == ,=== 的对比会更加严谨。

[10] == 10     // true
[10] === 10    // false
"10" == 10     // true
"10" === 10    // false
[] == 0        // true
[] === 0       // false
"" == false    // true 但是 true == "a" 是false
"" === false   // false

#23 - 转换数值的更快的方法
将字符串转换为数字是非常常见的。最简单和最快的(jspref)方式来实现,就是使用+(加)算法。

var one = '1';
var numberOne = +one; // Number 1

你也可以使用-(减号)算法的转换类型并且变成负数值。

var one = '1';
var negativeNumberOne = -one; // Number -1

#22 - 清空一个数组
你定义一个数组,并希望清空它的内容。通常,你会这样做:

var list = [1, 2, 3, 4];
function empty() {
    //清空数组
    list = [];
}
empty();

但是还有一种更高性能的方法。
你可以使用这些代码:

var list = [1, 2, 3, 4];
function empty() {
    //清空数组
    list.length = 0;
}
empty();

· list = [] 将一个变量指定个引用到那个数组,而其他引用都不受影响。这意味着,对于先前数组的内容的引用仍然保留在内存中,从而导致内存泄漏。
· list.length = 0 删除数组内的所有东西,这不需要引用任何其他的东西
然而,如果你有一个copy的数组(A和copy-A),如果你使用list.length = 0 删除其内容,副本也会失去它的内容。

var foo = [1,2,3];
var bar = [1,2,3];
var foo2 = foo;
var bar2 = bar;
foo = [];
bar.length = 0;
console.log(foo, bar, foo2, bar2);
//[] [] [1, 2, 3] []

StackOverflow上的更多详情:difference-between-array-length-0-and-array

#21 - 对数组排序进行"洗牌"(随机排序)
这段代码在这里使用Fisher Yates洗牌算法给一个指定的数组进行洗牌(随机排序)。

function shuffle(arr) {
    var i,
        j,
        temp;
    for (i = arr.length - 1; i > 0; i--) {
        j = Math.floor(Math.random() * (i + 1));
        temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
    return arr;    
};

案例:

var a = [1, 2, 3, 4, 5, 6, 7, 8];
var b = shuffle(a);
console.log(b);
// [2, 7, 8, 6, 5, 3, 1, 4]

网友@幻天芒在评论中提供的随机技巧:

arr.sort(function(){return Math.random() - 0.5;});

实测可用,感谢分享

#20 - 返回对象的函数能够用于链式操作
当创建面向对象的JavaScript对象的function时,函数返回一个对象将能够让函数可链式的写在一起来执行。

function Person(name) {
  this.name = name;
  this.sayName = function() {
    console.log("Hello my name is: ", this.name);
    return this;
  };
  this.changeName = function(name) {
    this.name = name;
    return this;
  };
}
var person = new Person("John");
person.sayName().changeName("Timmy").sayName();
//Hello my name is:  John
//Hello my name is:  Timmy

#19 - 字符串安全连接
假设你有一些类型未知的变量,你想将它们连接起来。可以肯定的是,算法操作不会在级联时应用:

var one = 1;
var two = 2;
var three = '3';
var result = ''.concat(one, two, three); //"123"

这样的连接不正是你所期望的。相反,一些串联和相加可能会导致意想不到的结果:

var one = 1;
var two = 2;
var three = '3';
var result = one + two + three; //"33"  而不是 "123"

谈到性能,对join和concat进行比较,他们的执行速度是几乎一样的。你可以在MDN了解更多与concat相关的知识

#18 - 更快的四舍五入
今天的技巧是关于性能。见到过双波浪线"~~"操作符吗?它有时也被称为double NOT运算符。你可以更快的使用它来作为Math.floor()替代品。为什么呢?
单位移~将32位转换输入-(输入+1),因此双位移将输入转换为-(-(输入+1)),这是个趋于0的伟大的工具。对于输入的数字,它将模仿Math.ceil()取负值和Math.floor()取正值。如果执行失败,则返回0,这可能在用来代替Math.floor()失败时返回一个NaN的时候发挥作用。

// 单位移
console.log(~1337)    // -1338
// 双位移
console.log(~~47.11)  // -> 47
console.log(~~-12.88) // -> -12
console.log(~~1.9999) // -> 1
console.log(~~3)      // -> 3
//失败的情况
console.log(~~[]) // -> 0 
console.log(~~NaN)  // -> 0
console.log(~~null) // -> 0
//大于32位整数则失败
console.log(~~(2147483647 + 1) === (2147483647 + 1)) // -> 0

虽然~~可能有更好的表现,为了可读性,请使用Math.floor()。

#17 - Node.js:让module在没被require的时候运行
在node里,你可以根据代是运行了require('./something.js')还是node something.js,来告诉你的程序去做两件不同的事情。如果你想与你的一个独立的模块进行交互,这是很有用的。

if (!module.parent) {
    // 运行 `node something.js`
    app.listen(8088, function() {
        console.log('app listening on port 8088');
    })
} else {
    // 使用 `require('/.something.js')`
    module.exports = app;
}

更多信息,请看the documentation for modules

#16 - 给回调函数传递参数
在默认情况下,你无法将参数传给回调函数,如下:

function callback() {
  console.log('Hi human');
}
document.getElementById('someelem').addEventListener('click', callback);

你可以采取JavaScript闭包的优点来给回调函数传参,案例如下:

function callback(a, b) {
  return function() {
    console.log('sum = ', (a+b));
  }
}
var x = 1, y = 2;
document.getElementById('someelem').addEventListener('click', callback(x, y));

什么是闭包呢?闭包是指一个针对独立的(自由)变量的函数。换句话说,闭包中定义的函数会记住它被创建的环境。了解更多请参阅MDN所以这种方式当被调用的时候,参数X/Y存在于回调函数的作用域内。
另一种方法是使用绑定方法。例如:

var alertText = function(text) {
  alert(text);
};
document.getElementById('someelem').addEventListener('click', alertText.bind(this, 'hello'));

两种方法在性能上有一些略微区别,详情参阅jsperf

          ### 2016-01-25 更新 ###          

posted @ 2016-01-30 11:01 野兽' 阅读(...) 评论(...) 编辑 收藏