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

时间:2023-03-09 20:18:57
[JS] ECMAScript 6 - String, Number, Function : compare with c#

字符串的扩展

js

  1. 字符的 Unicode 表示法
  2. codePointAt()
  3. String.fromCodePoint()
  4. 字符串的遍历器接口
  5. at()
  6. normalize()  【许多欧洲语言有语调符号和重音符号】
  7. includes(), startsWith(), endsWith()
  8. repeat()
  9. padStart(),padEnd()  【字符串补全长度的功能】
  10. matchAll()
  11. 模板字符串
  12. 实例:模板编译
  13. 标签模板
  14. String.raw()
  15. 模板字符串的限制

Ref: 模板字符串

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

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

通过tag来表示字符串。

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

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

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

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():检索字符串中的正则表达式的匹配

Method Description
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

  1. 二进制和八进制表示法
  2. Number.isFinite(), Number.isNaN()
  3. Number.parseInt(), Number.parseFloat()
  4. Number.isInteger()
  5. Number.EPSILON  【Number.EPSILON实际上是 JavaScript 能够表示的最小精度】
  6. 安全整数和 Number.isSafeInteger()  【判断一个整数是否落在这个范围之内】
  7. Math 对象的扩展
  8. 指数运算符

c#

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

Ref: Math Class【MSDN】

函数的扩展

js

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

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

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

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

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

例一:

let x = 1;

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

注意:参数中默认为是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() //
x //

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

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

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

一个不可以省略的参数:

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

一个可以省略的参数:

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;
}

那干脆禁止掉就好了!

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

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

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

'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

例子二:

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

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

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

(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]

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

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

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

const plus1 = a => a + 1;
const mult2 = a => a * 2; mult2(plus1(5))
//

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

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

参见:神奇的λ演算

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

  • call, apply

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

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

-- 理论 --

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

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

另外一个对象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:返回调用当前函数的那个函数。

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

  • 尾递归

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

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原型。

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