<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
有一天在思否社群看到有個問題,大致描述如下
const list = ['a', 'b', '-', 'c', 'd']; const reg = /[a-z]/g; const letters = list.filter(i => reg.test(i)); // letters === ['a', 'c']; // 如果正則不使用`g`標誌可以得到所有的字母 // 為什麼加入`g`之後就不可以了
對問題而言,遍歷中的i就是一個字元,不需要用到g。
但是就我對正則的理解(過於淺薄)感覺上有沒有g(只是全域性搜尋,不會匹配到就停下來)應該不影響,激發了我的好奇心。
上面題的建議寫法如下
const reg = /[a-z]/g; reg.test('a'); // => true reg.test('a'); // => false reg.test('a'); // => true reg.test('a'); // => false reg.test('a'); // => true
首先可以確定的表現一定是g導致的
開啟 MDN 仔細檢視g標誌的作用,得到結論和我的理解無二。
我猜想應該就是g可能啟用了某種快取,又因為reg相對過濾器是全域性變數,我將程式碼改為:
const list = ['a', 'b', '-', 'c', 'd']; const letters = list.filter(i => /[a-z]/g.test(i)); // letters === ['a', 'b', 'c', 'd'];
將正則宣告到每一次遍歷,得到結論就是正確的,驗證了我的猜想。也得到了,快取就是正則中的某個地方
下面我找到對應的原始碼來檢視問題的原因
由於最近在看 Rust,所以使用 Rust 編寫的原始碼檢視
開啟專案後,點選.進入 vscode 模式,command+p 搜尋 regexp 關鍵詞
進入test.rs檔案,command+f 搜尋/g
可以找到在 90 行有個last_index()
的測試
#[test] fn last_index() { let mut context = Context::default(); let init = r#" var regex = /[0-9]+(.[0-9]+)?/g; "#; // forward 的作用:更改 context,並返回結果的字串。 eprintln!("{}", forward(&mut context, init)); assert_eq!(forward(&mut context, "regex.lastIndex"), "0"); assert_eq!(forward(&mut context, "regex.test('1.0foo')"), "true"); assert_eq!(forward(&mut context, "regex.lastIndex"), "3"); assert_eq!(forward(&mut context, "regex.test('1.0foo')"), "false"); assert_eq!(forward(&mut context, "regex.lastIndex"), "0"); }
看到了有lastIndex關鍵字,這裡再已經大致猜到問題的原因了,g 標誌存在匹配後的最後一個下標,導致出現問題。
我們將視線移入到mod.rs檔案中,搜尋test
在 631 行看到了fn test()方法
pub(crate) fn test( this: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult<JsValue> { // 1. Let R be the this value. // 2. If Type(R) is not Object, throw a TypeError exception. let this = this.as_object().ok_or_else(|| { context .construct_type_error("RegExp.prototype.test method called on incompatible value") })?; // 3. Let string be ? ToString(S). let arg_str = args .get(0) .cloned() .unwrap_or_default() .to_string(context)?; // 4. Let match be ? RegExpExec(R, string). let m = Self::abstract_exec(this, arg_str, context)?; // 5. If match is not null, return true; else return false. if m.is_some() { Ok(JsValue::new(true)) } else { Ok(JsValue::new(false)) } }
在test()
方法中找到了Self::abstract_exec()
方法
pub(crate) fn abstract_exec( this: &JsObject, input: JsString, context: &mut Context, ) -> JsResult<Option<JsObject>> { // 1. Assert: Type(R) is Object. // 2. Assert: Type(S) is String. // 3. Let exec be ? Get(R, "exec"). let exec = this.get("exec", context)?; // 4. If IsCallable(exec) is true, then if let Some(exec) = exec.as_callable() { // a. Let result be ? Call(exec, R, « S »). let result = exec.call(&this.clone().into(), &[input.into()], context)?; // b. If Type(result) is neither Object nor Null, throw a TypeError exception. if !result.is_object() && !result.is_null() { return context.throw_type_error("regexp exec returned neither object nor null"); } // c. Return result. return Ok(result.as_object().cloned()); } // 5. Perform ? RequireInternalSlot(R, [[RegExpMatcher]]). if !this.is_regexp() { return context.throw_type_error("RegExpExec called with invalid value"); } // 6. Return ? RegExpBuiltinExec(R, S). Self::abstract_builtin_exec(this, &input, context) }
又在Self::abstract_exec()
方法中找到了Self::abstract_builtin_exec()
方法
pub(crate) fn abstract_builtin_exec( this: &JsObject, input: &JsString, context: &mut Context, ) -> JsResult<Option<JsObject>> { // 1. Assert: R is an initialized RegExp instance. let rx = { let obj = this.borrow(); if let Some(rx) = obj.as_regexp() { rx.clone() } else { return context.throw_type_error("RegExpBuiltinExec called with invalid value"); } }; // 2. Assert: Type(S) is String. // 3. Let length be the number of code units in S. let length = input.encode_utf16().count(); // 4. Let lastIndex be ℝ(? ToLength(? Get(R, "lastIndex"))). let mut last_index = this.get("lastIndex", context)?.to_length(context)?; // 5. Let flags be R.[[OriginalFlags]]. let flags = &rx.original_flags; // 6. If flags contains "g", let global be true; else let global be false. let global = flags.contains('g'); // 7. If flags contains "y", let sticky be true; else let sticky be false. let sticky = flags.contains('y'); // 8. If global is false and sticky is false, set lastIndex to 0. if !global && !sticky { last_index = 0; } // 9. Let matcher be R.[[RegExpMatcher]]. let matcher = &rx.matcher; // 10. If flags contains "u", let fullUnicode be true; else let fullUnicode be false. let unicode = flags.contains('u'); // 11. Let matchSucceeded be false. // 12. Repeat, while matchSucceeded is false, let match_value = loop { // a. If lastIndex > length, then if last_index > length { // i. If global is true or sticky is true, then if global || sticky { // 1. Perform ? Set(R, "lastIndex", +0
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45