![[JS] ECMAScript 6 - String, Number, Function : compare with c# [JS] ECMAScript 6 - String, Number, Function : compare with c#](https://image.shishitao.com:8440/aHR0cHM6Ly9ia3FzaW1nLmlrYWZhbi5jb20vdXBsb2FkL2NoYXRncHQtcy5wbmc%2FIQ%3D%3D.png?!?w=700&webp=1)
字符串的扩展
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 //
(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的方法:利用参数默认值,可以指定某一个参数不得省略,如果省略就抛出一个错误。
一个不可以省略的参数:
一个可以省略的参数:
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))
//
箭头函数还有一个功能,就是可以很方便地改写 λ 演算。
【λ 演算】就是一种特殊的语法所书写的匿名函数。
参见:神奇的λ演算
- 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原型。