[JS] ECMAScript 6 - String, Number, Function : compare with c#
字符串的扩展
js
- 字符的 Unicode 表示法
- codePointAt()
- String.fromCodePoint()
- 字符串的遍历器接口
- at()
- normalize() 【许多欧洲语言有语调符号和重音符号】
- includes(), startsWith(), endsWith()
- repeat()
- padStart(),padEnd() 【字符串补全长度的功能】
- matchAll()
- 模板字符串
- 实例:模板编译
- 标签模板
- String.raw()
- 模板字符串的限制
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():检索字符串中的正则表达式的匹配
Method | Description |
---|---|
exec |
A RegExp method that executes a search for a match in a string. It returns an array of information or null on a mismatch. |
test |
A RegExp method that tests for a match in a string. It returns true or false. |
match |
A String method that executes a search for a match in a string. It returns an array of information or null on a mismatch. |
search |
A String method that tests for a match in a string. It returns the index of the match, or -1 if the search fails. |
replace |
A String method that executes a search for a match in a string, and replaces the matched substring with a replacement substring. |
split |
A 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
- 二进制和八进制表示法
- Number.isFinite(), Number.isNaN()
- Number.parseInt(), Number.parseFloat()
- Number.isInteger()
- Number.EPSILON 【
Number.EPSILON
实际上是 JavaScript 能够表示的最小精度】 - 安全整数和 Number.isSafeInteger() 【判断一个整数是否落在这个范围之内】
- Math 对象的扩展
- 指数运算符
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
,以下三个变量在箭头函数之中也是不存在的,指向外层函数的对应变量:arguments
、super
、new.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指针。
-- 理论 --
当一个object没有某个方法,但是其他的有,我们可以借助call或apply用其它对象的方法来操作!
另外一个对象whiteDog = {food:"bone"},
我们不想对它重新定义say方法,我们可以通过call或apply用blackCat的say方法:blackCat.say.call(whiteDog)。
此时,say:function()中的this指针就成了whiteDog,this.food就变成了“bone”。
-- 实战 --
var domNodes = Array.prototype.slice.call(document.getElementsByTagName("*"));
- 双冒号运算符
箭头函数可以绑定this
对象,大大减少了显式绑定this
对象的写法(call
、apply
、bind
)。
但是,箭头函数并不适用于所有场合,所以现在有一个提案,提出了“函数绑定”(function bind)运算符,用来取代call
、apply
、bind
调用。
【提案暂时不看】
- 尾调用优化
尾调用(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原型。