[Effective JavaScript 筆記]第28條:不要信賴函數對象的toString方法

来源:http://www.cnblogs.com/wengxuesong/archive/2016/06/07/5567790.html
-Advertisement-
Play Games

js函數有一個非凡的特性,即將其源代碼重現為字元串的能力。 反射獲取函數源代碼的功能很強大,使用函數對象的toString方法有嚴重的局限性。toString方法的局限性ECMAScript標準對函數對象的toString方法的返回結果(即該字元串)並沒有任何要求。這意味著不同的js引擎將產生不同的 ...


js函數有一個非凡的特性,即將其源代碼重現為字元串的能力。

(function(x){
   return x+1
}).toString();//"function (x){   return x+1}"

反射獲取函數源代碼的功能很強大,使用函數對象的toString方法有嚴重的局限性。
toString方法的局限性
ECMAScript標準對函數對象的toString方法的返回結果(即該字元串)並沒有任何要求。這意味著不同的js引擎將產生不同的字元串,甚至產生的字元串與該函數並不相關。

如果函數是使用純js實現的,那麼js引擎會試圖提供該函數的源代碼的真實表示。

一個失敗的例子

(function(x){
     return x+1
}).bind(16).toString();//"function () { [native code] }"

失敗原因:

使用了由宿主環境的內置庫提供的函數。

  • 由於許多宿主環境中,bind函數是由其他編程語言實現的(通常是c++)。宿主環境提供的是一個編譯後的函數,在此環境下該函數沒有js的源代碼供顯示。

  • 由於標準允許瀏覽器引擎改變toString方法的輸出,很容易使編寫的程式一個js系統中正確運行,在其他js系統中卻無法正確運行。程式對函數的源代碼字元串的具體細節很敏感,即使js的實現有一點細微的變化都可能破壞程式。

  • 由toString方法生成的源代碼並不展示閉包中保存的與內部變數引用相關的值

(function(x){
   return function(y){
      return x+y;
   }
})(42).toString();//"function (y){      return x+y;   }"

註意:儘管函數實際上是一個綁定x為42的閉包,但結果字元串仍然包含一個引用x的變數。

從某種意義上說,js的toString方法的這些局限使其用來提取函數源代碼並不是特別有用和值得信賴。通常應該避免使用它。對提取函數源代碼相當複雜的使用應當採用精心製作的js解釋器和處理庫。將js函數看作是一個不該違背的抽象是最穩妥的。

提示

  • 當調用函數的toString方法時,並沒有要求js引擎能夠精確地獲取到函數的源代碼

  • 由於在不同的引擎下調用toString方法的結果可能不同,所以絕不要信賴函數源代碼的詳細細節

  • toString方法的執行結果並不會暴露存儲在閉包中的局部變數

  • 通常情況下,應該避免使用函數對象的toString方法

附錄一:toString方法

不同數據類型調用toString方法的結果。
toString方法是Object原型對象中的一個方法,所以繼承自這個類的對象都會繼承這個方法,並可以對toString方法進行覆蓋。
js標準庫中的5種簡單數據類型:Undefined,Null,Boolean,Number和String。還有一種複雜的數據類型Object,Object的本質是一組無序的名值對組成。

簡單數據類型

//數字
(Undefined).toString();//"error"
(Null).toString();//error
(true).toString();//"true"
(1).toString();//"1"
('111').toString();//"111"

可以看出其中除了Undefined和Null類型外,為什麼其它幾個基本類型可以運行呢。
這在我們之前的文章《[Effective JavaScript 筆記] 第4條:原始類型優於封閉對象》中講到,當簡單數據類型調用toString方法會首先把原始類型轉換成包裝對象。
此時對應的包裝對象為

  • 數字為Number對象

  • 布爾值為Boolean對象

  • 字元串為String對象

這些對象也都是繼承自Object對象的,並重寫了各自的toString方法。
但Undefined類型和Null類型都只有一個值undefined,null,並沒有對應的封裝對象。
雖然typeof null的值是"object",但並沒用。

引用類型

//Object對象
({a:10,b:20}).toString();//"[object Object]"//Date對象
(new Date).toString();//"Tue Jun 07 2016 15:37:15 GMT+0800 (中國標準時間)"//RegExp對象
(/^sss$/g).toString();//"/^sss$/g"//Function對象function aa(){return "bb"}
aa.toString();//"function aa(){return "bb"}"//window對象window.toString();//"[object Window]"//Math對象Math.toString();//"[object Math]"

看到上面的toString方法,Object,window,Math是使用Object原型方法。其它對象都使用了自身覆蓋的toString方法。

typeof操作符

對以上所有類型使用typeof操作符時會得到以下的結果

typeof 1;//"number"typeof '1';//"string"typeof true;//"boolean"typeof undefined;//"undefined"typeof (function a(){});//"function"typeof null;//"object"typeof {};//"object"typeof (new Date);//"object"typeof [];//"object"typeof window;//"object"typeof Math;//"object"typeof (/sdfsf/g);//"object"

可以看出,想使用單單的typeof操作符來對類型進行判斷幾乎是不可能的。
有人可能會說對於返回object字元串,可以使用構造函數來判斷類型即instanceOf方法。

({}) instanceof Object;//true
(new Date) instanceof Date;//true
([]) instanceof Array;//true
(/sdfsf/g) instanceof RegExp;//true

然後null類型只要

var a=null;
a===null;//true;

好像可以實現下麵這樣的類型判斷代碼了

function getType(obj){
    if(typeof obj !== 'object'){
        return typeof obj;
    }else{   
        if(obj===null){
            return 'null';
        }
        if(obj===window){
            return 'window'; 
        }
        if(obj===Math){
            return 'Math'
        }      
        if((obj) instanceof Date){
            return 'date';
        }
        if((obj) instanceof Array){
            return 'array';
        }
        if((obj) instanceof RegExp){
            return 'regexp';
        } 
        if((obj) instanceof Object){
            return 'object';
        }

    }
}

上面代碼是否可以運行測試一下,並沒有問題

getType(1);//"number"
getType(true);//"boolean"
getType('1');//"string"
getType(undefined);//"undefined"
getType(function(){});//"function"
getType(/sf/);//"regexp"
getType(null);//"null"
getType(window);//"window"
getType({});//"object"
getType([]);//"array"

但這裡要註意的一個問題就是,這個代碼里的對於object類型的檢測一定要放到最後面。
如下所示,所有對象都是繼承自Object,所以instanceof檢測所有對象是否為Object類型的實例返回都是true

([]) instanceof Array;//true
([]) instanceof Object;//true

看到以上代碼是不是覺得太複雜麻煩了,有沒有一種更簡單的方法來對類型進行判斷呢?答案當然是有,下麵來看toString方法的運用。

toString應用

如上面所說,繼承自Object的對象都有toString方法,但每個對象實現了各自的toString方法,導致無法用toString方法進行類型判斷。這裡可以利用之前講到過的call或apply方法來調用Object.prototype.toString方法。

function getType(obj){
   var toString=Object.prototype.toString;
   return toString.call(obj);   
}

測試一下各類型會得到如下結果

getType(1);//"[object Number]"
getType(true);//"[object Boolean]"
getType('1');//"[object String]"
getType(undefined);//"[object Undefined]"
getType(function(){});//"[object Function]"
getType(/sf/);//"[object RegExp]"
getType(null);//"[object Null]"
getType(window);//"[object Window]"
getType({});//"[object Object]"
getType([]);//"[object Array]"
getType(Math);//"[object Math]"

所有類型都可以區分出來,是不是很簡單呀?這個是不是就可以萬事大吉了呢,錯,還有個特殊的值沒有處理NaN.

getType(NaN);//"[object Number]"

NaN並不是一個Number類型的數,它是表達不是一個數字的值,這裡對這個值也要進行處理。可以關註之前文章《
》里關於NaN的內容。處理代碼如下

function isReallyNaN(x){
   return x!==x;
}

完整的版本

function getType(obj){
   if(obj!==obj)return "NaN";
   var toString=Object.prototype.toString;
   return toString.call(obj);   
}
getType(NaN);//"NaN"

備忘:

這裡需要去瞭解一下,js解釋器的知識。
相關的鏈接有:
javascript設計模式之解釋器模式詳解
javascript設計模式 - 解釋器模式(interpreter)
Chrome V8


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • urllib 模塊是一個高級的 web 交流庫,其核心功能就是模仿web瀏覽器等客戶端,去請求相應的資源,並返回一個類文件對象。urllib 支持各種 web 協議,例如:HTTP、FTP、Gopher;同時也支持對本地文件進行訪問。但一般而言多用來進行爬蟲的編寫,而下麵的內容也是圍繞著如何使用 u ...
  • 本章內容: 創建類和對象 面向對象三大特性(封裝、繼承、多態) 類的成員(欄位、方法、屬性) 類成員的修飾符(公有、私有) 類的特殊成員 面向對象編程是一種編程方式,此編程方式的落地需要使用 “類” 和 “對象” 來實現,所以,面向對象編程其實就是對 “類” 和 “對象” 的使用。 類就是一個模板, ...
  • 1、PDF下載 蘋果Swift編程語言入門教程【完整中文版】http://www.code4app.com/thread-7878-1-1.htmlThe Swift Programming Language中文完整版 http://www.code4app.com/thread-7966-1-2. ...
  • 回顧Java平臺上Web開發歷程來看,從Servlet出現開始,到JSP繁盛一時,然後是Servlet+JSP時代,最後演化為現在Web開發框架盛行的時代。一般接觸到一個新的Web框架,都會想問這個框架優勢在哪?或者比其他框架好在哪裡?如果沒有使用Spring MVC框架,而是使用其他框架並且能夠很 ...
  • 之前我們使用io流,都是需要一個中間數組,管道流可以直接輸入流對接輸出流,一般和多線程配合使用,當讀取流中沒數據時會阻塞當前的線程,對其他線程沒有影響 定義一個類Read實現Runable介面,實現run()方法,構造方法傳遞PipedInputStream對象 讀取流裡面的數據 定義一個類Writ ...
  • 僅供參考 java springMvc mybatis mylsq 項目搭建 1.開發環境: window 64、jdk 1.7.0_51、eclipse、tomcat 7 2.jdk安裝與環境變數配置 http://jingyan.baidu.com/article/6dad5075d1dc40a ...
  • 條件語句,是程式中根據條件是否成立進行選擇執行的一類語句,這類語句在實際使用中,難點在於如何準確的抽象條件。例如實現程式登錄功能時,如果用戶名和密碼正確,則進入系統,否則彈出“密碼錯誤”這樣的提示框等。 本部分對於條件語句的介紹,重點在於語法講解和基本的使用,更詳細的使用參看後續的綜合示例部分。 在 ...
  • 從遇到問題開始 當人們要做一個軟體系統時,一般總是因為遇到了什麼問題,然後希望通過一個軟體系統來解決。 比如,我是一家企業,然後我覺得我現線上下銷售自己的產品還不夠,我希望能夠線上上也能銷售自己的產品。所以,自然而然就想到要做一個普通電商系統,用於實現線上銷售自己企業產品的目的。 再比如,我是一家互 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...