[JS] ECMAScript 6 - String, Number, Function : compare with c#

 

 

 

字符串的扩展 

js

 

Ref: 模板字符串

策略:${var},放在反引号中!

通过tag来表示字符串。

 使用for输出完整的字符串,记得最后一个strings[strings.length-1]。

 

c#

Ref: 一个非常好的C#字符串操作处理类StringHelper.cs

Ref: 字符串(C# 编程指南)

Ref: 常用C#字符串函数大全

 

 

正则表达式的扩展

js

修饰符:

    • i(intensity)  :大小写不敏感。
    • g(global)    :全局查找,对于一些特定的函数,将迭代完整的字符串,获得所有的匹配结果,而不仅仅在得到第一个匹配后就停止进行。
    • m(multiple):检测字符串中的换行符,主要是影响字符串开始标识符^和结束标识符$的使用。
var urlReg     = /(\w+):\/\/([\w.]+)\/(\S*)/;
var myHomepage = "http://www.wanjilong.com/homepage";
var result     = myHomepage.match(urlReg);
console.log(result);
for (var i = 0, len = result.length; i < len; ++i) { console.log(i, result[i]); }

  

Ref: JavaScript 正则表达式

Ref: JavaScript RegExp 对象

var patt = new RegExp(pattern,modifiers);

或者,更简单的方法

var patt = /pattern/modifiers;

test():一个字符串是否匹配某个模式

exec():检索字符串中的正则表达式的匹配

MethodDescription
exec RegExp method that executes a search for a match in a string. It returns an array of information or null on a mismatch.
test RegExp method that tests for a match in a string. It returns true or false.
match String method that executes a search for a match in a string. It returns an array of information or null on a mismatch.
search String method that tests for a match in a string. It returns the index of the match, or -1 if the search fails.
replace String method that executes a search for a match in a string, and replaces the matched substring with a replacement substring.
split String method that uses a regular expression or a fixed string to break a string into an array of substrings.

 

c#

Ref: C# 正则表达式

匹配:匹配了以 'm' 开头以 'e' 结尾的单词

using System;
using System.Text.RegularExpressions;

namespace RegExApplication
{
   class Program
   {
      private static void showMatch(string text, string expr)
      {
         Console.WriteLine("The Expression: " + expr);
         MatchCollection mc = Regex.Matches(text, expr);
         foreach (Match m in mc)
         {
            Console.WriteLine(m);
         }
      }
      static void Main(string[] args)
      {
         string str = "make maze and manage to measure it";

         Console.WriteLine("Matching words start with 'm' and ends with 'e':");
         showMatch(str, @"\bm\S*e\b");
         Console.ReadKey();
      }
   }
}

替换:替换掉多余的空格

using System;
using System.Text.RegularExpressions;

namespace RegExApplication
{
   class Program
   {
      static void Main(string[] args)
      {
         string input       = "Hello   World   ";
         string pattern     = "\\s+";
         string replacement = " ";
         Regex rgx = new Regex(pattern);
         string result = rgx.Replace(input, replacement);

         Console.WriteLine("Original String: {0}", input);
         Console.WriteLine("Replacement String: {0}", result);    
         Console.ReadKey();
      }
   }
}

 

 

数值的扩展

js

 

c#

Ref: C#数学计算包 Math.NET

Ref: Math Class【MSDN】

 

 

函数的扩展

js

  • 函数的 length 属性
(function (a) {}).length            // 1
(function (a = 5) {}).length        // 0
(function (a, b, c = 5) {}).length  // 2

 

指定了默认值后,length属性将失真。

后文的 rest 参数也不会计入length属性

(function(...args) {}).length // 0

/**
* 只计算最后一个有默认值参数之前的”未默认初始化“的参数的个数
*/ (
function (a = 0, b, c) {}).length // 0 (function (a, b = 1, c) {}).length // 1

  

  • 作用域

例一

let x = 1;

function f(y = x) {   # 调用函数f时,参数形成一个单独的作用域
  let x = 2;          # 这一条语句 其实没用
  console.log(y);
}

f() // 1

注意:参数中默认为是let,故锁住了作用域

例二

let foo = 'outer';

function bar( func = () => foo ) {  // 同理,foo是外层的
  let foo = 'inner';
  console.log(func());
}

bar(); // outer

 

例三:这里是三个作用域的x。

var x = 1;
function foo( x, y = function() { x = 2; } ) {  // 参数中的x是单独的作用域 var x = 3; // 函数内部的x是另一个作用域   y(); // 执行y后,内部变量x和外部全局变量x的值都没变 console.log(x); } foo() // 3 x // 1

这里,只有两个作用域,函数参数和函数内部的作用域是一样的。

var x = 1;
function foo(x, y = function() { x = 2; }) {
  x = 3;                                        // 内部变量x就指向第一个参数x
  y();
  console.log(x);
}

foo() // 2
x // 1

  

  • 应用

相当棒的tricky的方法:利用参数默认值,可以指定某一个参数不得省略,如果省略就抛出一个错误。

一个不可以省略的参数:

一个可以省略的参数:

function foo(optional = undefined) { ··· }

 

 

  • rest 参数

比过去的argument要好

// arguments变量的写法
function sortNumbers() {
  return Array.prototype.slice.call(arguments).sort();
}

// arguments对象不是数组,而是一个类似数组的对象。
// 所以为了使用数组的方法,必须使用Array.prototype.slice.call先将其转为数组。

-----------------------------------------------------------------------
// rest参数的写法 const sortNumbers = (...numbers) => numbers.sort();

// rest 参数就不存在这个问题,它就是一个真正的数组,数组特有的方法都可以使用。

 

rest 参数必须在尾部

rest 参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。

// 数组参数的典型用法

function
push(array, ...items) { items.forEach(function(item) { array.push(item); console.log(item); }); } var a = []; push(a, 1, 2, 3)

 

  • 严格模式

只要参数使用了默认值、解构赋值、或者扩展运算符,就不能显式指定严格模式。

产生了矛盾点:"只有从函数体之中,才能知道参数是否应该以严格模式执行,但是参数却应该先于函数体执行"

// 报错
function doSomething(value = 070) {
  'use strict';  # 在执行这里前,如何处理参数是个问题
  return value;
}

那干脆禁止掉就好了!

 

但并不是说,这跟‘严格模式’的初衷有冲突,两种方法可以规避这种限制。

第一种,是设定全局性的严格模式,这是合法的。

'use strict';

function doSomething(a, b = a) {
  // code
}

第二种,是把函数包在一个无参数的立即执行函数里面。

const doSomething = (function () {
  'use strict';
  return function(value = 42) {
    return value;
  };
}());

参考:[JS] Why "strict mode" here

 

  • name 属性
const bar = function baz() {};

// ES5
bar.name // "baz"

// ES6
bar.name // "baz"

其他情况见原文。

 

  • 箭头函数

同样的,注意大括号(代码块) 是否作为了返回值。

// 报错
let getTempItem = id => { id: id, name: "Temp" };

// 不报错
let getTempItem = id => ({ id: id, name: "Temp" });

// 虽然可以运行,但会得到错误的结果
// 由于引擎认为大括号是代码块,所以执行了一行语句a: 1

// 这时,a可以被解释为语句的标签,因此实际执行的语句是1;
// 然后函数就结束了,没有返回值。
let foo = () => { a: 1 };
foo() // undefined

// 如果箭头函数只有一行语句,且不需要返回值,可以采用下面的写法,
// 就不用写大括号了
let fn = () => void doesNotReturn();

 

便捷一:箭头函数可以与变量解构结合使用。

const full = ({ first, last }) => first + ' ' + last;

// 等同于 function full(person) { return person.first + ' ' + person.last; }

便捷二:简化回调函数

// 正常函数写法
[1,2,3].map(function (x) {
  return x * x;
});

// 箭头函数写法
[1,2,3].map(x => x * x);

便捷三:与 rest 参数相结合。

const numbers = (...nums) => nums;
numbers(1, 2, 3, 4, 5)
// [1,2,3,4,5]

const headAndTail = (head, ...tail) => [head, tail]; headAndTail(1, 2, 3, 4, 5) // [1,[2,3,4,5]]

 

箭头函数的使用注意点

(1)函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。 

this对象的指向是可变的;

但是在箭头函数中,它是固定的。

作用一

例子一:

function foo() {
  setTimeout( () => {
    console.log('id:', this.id);
  }, 100 );
}

var id = 21;
// 对象{id:42}作为了参数
foo.call({ id: 42 });
// id: 42

例子二:

Timer函数内部设置了两个定时器,分别使用了箭头函数和普通函数。

前者的this绑定定义时所在的作用域(即Timer函数),

后者的this指向运行时所在的作用域(即全局对象)。

所以,3100 毫秒之后,timer.s1被更新了 3 次,而timer.s2一次都没更新。

 

作用二

箭头函数可以让this指向固定化,这种特性很有利于封装回调函数

注意:

this指向的固定化,并不是因为箭头函数内部有绑定this的机制,实际原因是箭头函数根本没有自己的this

导致内部的this就是外层代码块的this。

除了this,以下三个变量在箭头函数之中也是不存在的,指向外层函数的对应变量:argumentssupernew.target

// ES6
function foo() {
  setTimeout(() => {
    console.log('id:', this.id);
  }, 100);
}

// ES5
function foo() {
  var _this = this;

  setTimeout(function () {
    console.log('id:', _this.id);
  }, 100);
}

请问下面的代码之中有几个this

 

 

(2)不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。 

(3)可以用 rest 参数,但不可以使用arguments对象,该对象在函数体内不存在。

(4)不可以使用yield命令,因此箭头函数不能用作 Generator 函数。

 

  • 嵌套的箭头函数

如下,可见箭头函数带来的好处,有点builder or pipeline的感觉。

function insert(value) {
  return { into: function (array) {
    return { after: function (afterValue) {
      array.splice(array.indexOf(afterValue) + 1, 0, value);
      return array;
    }};
  }};
}

insert(2).into([1, 3]).after(1); //[1, 2, 3]

上面这个函数,可以使用箭头函数改写。【记得加圆括号

可读性提升,也可以采用下面的写法

const plus1 = a => a + 1;
const mult2 = a => a * 2;

mult2(plus1(5))
// 12

 

箭头函数还有一个功能,就是可以很方便地改写 λ 演算。

λ 演算】就是一种特殊的语法所书写的匿名函数。

参见:神奇的λ演算

 

  • call, apply

在原生js中会有三个很常见的函数,call, apply, bind。他们的作用就是改变当前函数的this指针。

Ref: 如何理解和熟练运用js中的call及apply?

-- 理论 --

当一个object没有某个方法,但是其他的有,我们可以借助call或apply用其它对象的方法来操作!

另外一个对象whiteDog = {food:"bone"},

我们不想对它重新定义say方法,我们可以通过call或apply用blackCat的say方法:blackCat.say.call(whiteDog)。

此时,say:function()中的this指针就成了whiteDog,this.food就变成了“bone”。

-- 实战 --

用的比较多的,通过document.getElementsByTagName选择的dom 节点是一种类似array的array。
它不能应用Array下的push,pop等方法。我们可以通过:
var domNodes = Array.prototype.slice.call(document.getElementsByTagName("*"));
这样domNodes就可以应用Array下的所有方法了。


  • 双冒号运算符

箭头函数可以绑定this对象,大大减少了显式绑定this对象的写法(callapplybind)。

但是,箭头函数并不适用于所有场合,所以现在有一个提案,提出了“函数绑定”(function bind)运算符,用来取代callapplybind调用。

【提案暂时不看】

 

  • 尾调用优化

尾调用(Tail Call)是函数式编程的一个重要概念,本身非常简单,一句话就能说清楚,就是指某个函数的最后一步是调用另一个函数。

function f(x) {
  if (x > 0) {
    return m(x)
  }
  return n(x);
}

 

"尾调用"由于是函数的最后一步操作,所以不需要保留外层函数的调用帧,因为调用位置、内部变量等信息都不会再用到了,只要直接用内层函数的调用帧,取代外层函数的调用帧就可以了。

大大节省内存!

function f() {
  let m = 1;
  let n = 2;
  return g(m + n);
}
f();

// 等同于
function f() {
  return g(3);
}
f();

// 等同于
g(3);

反例子:

function addOne(a){
  var one = 1;
  function inner(b){
    return b + one;  // 因为这里,所以被迫在调用inner时还需要保留住var one,也就不是tail call了
  }
  return inner(a);
}

 

NB: ES6 的尾调用优化只在严格模式下开启,正常模式是无效的。

这是因为在正常模式下,函数内部有两个变量,可以跟踪函数的调用栈。

    • func.arguments:返回调用时函数的参数。
    • func.caller:返回调用当前函数的那个函数。

 

  • 尾递归

对于尾递归来说,由于只存在一个调用帧,所以永远不会发生“栈溢出”错误。  

function factorial(n) {
  if (n === 1) return 1;
  return n * factorial(n - 1);
}

factorial(5) // 120

--------------------------------------------------
改写成尾递归,只保留一个调用记录,复杂度由O(n) --> O(1) 。
function factorial(n, total) {
  if (n === 1) return total;
  return factorial(n - 1, n * total);
}

factorial(5, 1) // 120

 

可见,诀窍就在于:把所有用到的内部变量改写成函数的参数。

还有就是:只需要知道循环可以用递归代替,而一旦使用递归,就最好使用尾递归。

其他部分,柯里化(currying)【将多参数的函数转换成单参数的形式】详见链接。

 

尾递归优化只在严格模式下生效。

在正常模式下,或者那些不支持该功能的环境中,就是自己实现尾递归优化。 

减少调用栈?就是采用“循环”换掉“递归”。

详见原链接。

 

  • 函数参数的尾逗号

ES2017 允许函数的最后一个参数有尾逗号(trailing comma)。

这样的规定也使得,函数参数数组对象的尾逗号规则,保持一致了。

 

 

函数是一种对象

  • 到处都是”Object" 

基本值类型不是对象(number、string、Boolean、Undefined);

剩下的引用类型(函数、数组、null...)都是对象。

 

对象是通过函数创建的,而函数又是一种对象。那么这是为什么呢?这就牵扯到prototype原型。 

 

 

 

posted @ 2018-02-06 12:52  郝壹贰叁  阅读(326)  评论(0编辑  收藏  举报