快報前方測試傳來情報:IOS版在IOS9系統下沒法請求和展現文中廣告!html
這裏因爲 webpack 默認的打包方式會將模塊打包爲 eval() 執行塊,很是不利於定位代碼具體位置。所以我將 webpack 打包配置的 devtool 修改成 "source-map", 這樣打包出來的js基本跟源碼一致。webpack
最終,終端同窗給出報錯日誌以下:ios
報錯信息爲:Attempting to change configurable attribute。但因爲是編譯後polyfill以後的代碼,由於較難判斷出來是誰形成的。只看到報錯的函數爲:_definePropertyweb
通過仔細閱讀報錯消息,咱們能夠得出結論:這是由於咱們修改了一個 unconfigurable 的屬性。ajax
咱們知道,在 ES5 中,JavaScript 提供了一個 Object.defineProperty 的方法,從而能夠定義屬性的 descriptor;而對於定義爲 "configurable: false" 的屬性來講,它是沒法被修改的(特指經過Object.defineProperty再次修改描述,或經過 delete 運算符刪除),而對於定義爲 "writebale: false" 的屬性來講,是指的它沒法被賦值運算符"="來修改。瀏覽器
那麼,很明顯咱們的錯誤提醒說明咱們的代碼中作了 Object.defineProperty 或 delete 一個不可更改的屬性的操做。因而,咱們看看是誰調用了 _defineProperty 這個函數,最終找到bundle.js中這麼兩句代碼:babel
_defineProperty(KbArticleCenter, "name", 'kb-article-center'); _defineProperty(KbArticleCenter, "instances", []);
其中 KbArticleCenter 在個人源碼中是一個 class ,而 name 和 instances 是兩個類靜態成員。源碼以下所示:dom
class KbArticleCenter { static name = 'kb-article-center' static instances = [] // ......... 省略一堆類的成員定義代碼 }
難道說:類的靜態成員在 babel 編譯以後,會出現不兼容 IOS9 的狀況? 帶着疑問我去搜索了 plugin-proposal-class-properties 插件的issue,但並無收穫。函數
最後,仍是回到編譯後的代碼來查看,突然間恍然大悟,咱們知道:一個 class 類在 babel 編譯後實際上會轉換爲一個普通的 JavaScript 函數,以下:測試
function KbArticleCenter(options) { // ..... 省略一坨構造函數代碼 this.init(); }
而咱們的靜態成員則會被經過 Object.defineProperty 的方式直接添加到該函數自身上面。例如咱們在類型中定義的 static name 屬性則被轉變爲: _defineProperty(KbArticleCenter, "name", 'kb-article-center');
然而,別忘了,對於 JavaScript 函數來講,它自身便擁有一個同名的 name 屬性,咱們這裏若是又經過 defineProperty 的方式重寫它,則意味着必需要求原來的 name 屬性是能夠 configurable 的 (即 configuable: true)。
在正常的現代瀏覽器中,咱們一個 JavaScript 函數的 name 屬性其實默認 configuable 是 true 的。例如以下代碼的輸出結果中顯示 name 是可 configurable 的:
var foo = function() {} Object.getOwnPropertyDescriptor(foo, 'name') // configurable: true // enumerable: false // value: "foo" // writable: false
然而,我深入懷疑在 safari9 當中,name 屬性是 uncofigurable 的。因爲沒有測試機,因此直接將 name 屬性改爲 compName,從新打包交給測試驗證!
交給測試驗證後,終端看日誌出現了新的報錯:"Unhandled Promise Rejection: NotSupportedError (DOM Exception 9)"
仔細觀察錯誤堆棧,發現問題出如今源碼 initDom 函數的 createContextualFragment 位置處。咱們貼出此處的代碼:
const frag = this.adEl = document.createRange().createContextualFragment(renderedHtml).firstElementChild
此處代碼的功能是基於 artTemplate 渲染出來的dom字符串生成一個原生dom節點,這裏的思路是藉助了 Range 類型的 createContextualFragment 方法。其中 Range 接口表示一個包含節點與文本節點的一部分的文檔片斷,經過 createContextualFragment 便可把一段html內容轉換爲 DocumentFragment 文檔片斷。
爲何不用 document.createDocumentFragment來建立文檔片斷呢?由於咱們這裏是基於字符串建立dom,而不是直接建立dom。
然而,查閱MDN發現,createContextualFragment 是一個實驗性的 API,儘可能不要在生產環境使用。事實上咱們發現,整個 Range API 在 ios9 都不可用:
所以,果斷換一個實現思路:經過 innerHTML 把dom字符串轉換爲一個父div的子dom節點,而後經過父div的 firstElementChild 方法把這個dom節點拿出來:
const tmp = document.createElement('div') tmp.innerHTML = renderedHtml const frag = this.adEl = tmp.firstElementChild
而firstElementChild的兼容性就好多了:
至此,問題算解決了。
不一樣版本的瀏覽器的確會有不少細節上不一樣的實現,咱們寫代碼時最好多注意些:
* 對於已知的差別,作好特性檢測和兼容
* 對於未知的,儘可能寫代碼時防患於未然。例如本文的場景下,就要記得不要採用跟一些保留字衝突的屬性名,很明顯:假如基礎知識更紮實一些便不會犯下錯誤。
* 對於一些較偏門的 API (尤爲是從網上抄來的),要最好去查一下規範和 can i use 的支持狀況