Blog icon indicating copy to clipboard operation
Blog copied to clipboard

Vue兼容IE的研究、实现

Open abeet opened this issue 6 years ago • 5 comments

Vue兼容IE的研究、实践

背景

公司前端框架使用的Vue.js,因为它基于Object.defineProperties来监听数据变化,而IE8是不支持Object.defineProperties的,所以注定不能兼容IE8。
Vue作者尤雨溪也无意让Vue支持IE8,见 https://github.com/vuejs/vuejs.org/issues/50
目前Vue.js我们只用在后台界面,网站前台由于要兼容IE8,使用的是jQuery或Backbone,造成技术栈不统一,工程化不够。 为此想寻求Vue.js兼容IE8的方案。

可行性调研

github上搜索"vue IE8" 找到一个项目 https://github.com/wcflmy/VM4IE8
这个项目的数据监听是通过一个把一个model的数据,附加到一个dom元素上, 然后ie8下使用Object.defineProperty来监听dom上属性的改变
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty#Internet_Explorer_8_specific_notes
从Mozilla的资料上来看,有两大问题,
一个问题:因为model是附在dom上的,会有很多的dom属性,不能再用for遍历,
二个问题:数据键名要避开所有的dom属性名,
综上:基本等于没有用

另外一个未完成的项目,如
https://github.com/wusfen/vm
https://github.com/wusfen/vue-ie
发邮件问了作者,他用的类似Angular的检测机制,对所有的异步的函数、方法,进行复写、注入 $foceUpdate ,如 ajax, setTimeout, Image.onload, ... ,基本覆盖常用情况。 不太喜欢Angular里面的zone.js对各种异步函数的污染,在实践中有遇到过因为Promise被污染导致某些库不正常(axios?)的情况。

https://www.cnblogs.com/rubylouvre/p/3598133.html
感觉还是Avalon里用到的VBScript方案更靠谱一些
要注意的是 value作为VBScript的特殊值也不能使用作属性名。

在网上还有一个模板解析器用到了VBScript来作defineProperties数据检测
https://github.com/purplebamboo/pat/blob/master/build/latest/pat.js
应该是有借鉴Avalon

至此定下方向,使用VBScript实现setter/getter来代替Object.defineProperties来监听数据变化

在github上再搜索了一些VBScript实现setter/getter的资料
http://webreflection.blogspot.com/2011/03/rewind-getters-setters-for-all-ie-with.html
https://gist.github.com/jeffrafter/189354
有人提到在dojo里就用了VBScript实现setter/getter, 由此看来这不是Avalon的首创。只是很奇怪dojo把VBScript在iframe里去执行,为什么不用exeScript?
https://download.dojotoolkit.org/release-1.9.7/dojo-release-1.9.7/dojox/lang/observable.js.uncompressed.js

实现过程

由于Vue1的代码逻辑比Vue2更直观,并且没有使用虚拟dom,有利于debug,所以我计划先实现Vue1兼容IE8,下次再实现Vue2兼容IE8。

VBScript实现setter/getter测试

将Avalon中的 createViewModel 抽取出来, 在IE8下写了一个页面测试双向绑定,成功!

将Pat.js中的define方法抽取出来, 在IE8下写了一个页面测试双向绑定成功,成功!

先解决vue1在IE8下的语法错误

然后是vue在不管双向绑定的情况下,在ie8下跑起来, 各种要shim的方法, 包括但不限于以下

  • IE8下Array实例没有forEach等方法
  • IE8下String实例没有trim等方法
  • IE8下Function实例没有bind等方法
  • IE8下Object没有keys等方法
  • 在测试中发现IE8下dom元素的cloneNode方法有bug,多个text子节点只clone了一个
  • IE8下input元素没有change事件 当然还有最重要的一个,IE8下没有Object.defineProperties

先找一个es5-shim 把数组等基本对象的成员方法补齐。 https://github.com/es-shims/es5-shim

由于一般的es6-shim库,代码量大,而且对Object.defineProperties的实现根本不实用,所以vue1中涉及的es6特有的方法,我要一个个补齐,而不再引用某个库。
先找了 es5 shim es6 shim 参考
https://github.com/seamus-oconnor/lift-js/tree/master/src/modules/es5/object
https://github.com/paulmillr/es6-shim/blob/master/es6-shim.js
补齐了以下几个方法/属性

Object.freeze //无法实现,仅不抛错
Object.isExtensible //无法实现,仅不抛错
Object.create
Object.getOwnPropertyNames
Object.assign
Element.prototype.addEventListener
Element.prototype.removeEventListener
Event.prototype.target
Event.prototype.stopPropagation
Event.prototype.preventDefault
Object.defineProperties // 对于非dom元素,还需要调用 Object.definePropertiesByVBS才返回有监听属性改变的VBScript对象
Object.defineProperty  // 对于非dom元素,还需要调用 Object.definePropertiesByVBS才返回有监听属性改变的VBScript对象

至此vue.js在IE8下载入时不再抛出脚本错误

最关键的一步,实现Object.definePropertiesByVBS

上面的实现过程中,Object.definePropertyObject.defineProperties被实现为仅配置一个js对象有哪些属性要被监听,调用 Object.definePropertiesByVBS 后,会返回一个新的VBScript对象,这个对象上会监听属性的改变。
注意:是新的VBScript对象,不是原来的js对象
Object.definePropertiesByVBS的大体逻辑如下

window.execScript(['Function parseVB(code)', '\tExecuteGlobal(code)', 'End Function'].join('\r\n'), 'VBScript')
Object.defineProperty = function (obj, prop, desc) {
  obj.__defindeProperties__[prop] = desc
}
var VB_ID = 0
Object.definePropertiesByVBS = function (obj) {
    var cb_poll = {}
    var className = 'VB' + VB_ID++
    buffer.push('Class ' + className)
    for (var key in obj.__defindeProperties__) {
      var desc = obj.__defindeProperties__[key]
      cb_poll[key + '_set'] = desc.set
      buffer.push(
        '\tPublic Property Let [' + key + '](value)',
        '\t\tCall [__proxy](me, "set", "' + key + '", value)',
        '\tEnd Property',
        '\tPublic Property Set [' + key + '](value)',
        '\t\tCall [__proxy](me, "set", "' + key + '", value)',
        '\tEnd Property'
      )
      cb_poll[key + '_get'] = desc.get
      buffer.push(
        '\tPublic Property Get [' + key + ']',
        '\tOn Error Resume Next', 
        '\t\tSet [' + key + '] = [__proxy](me, "get", "' + key + '")',
        '\tIf Err.Number <> 0 Then',
        '\t\t[' + key + '] = [__proxy](me, "get", "' + key + '")',
        '\tEnd If',
        '\tOn Error Goto 0',
        '\tEnd Property'
      )
    }
    buffer.push('End Class')
    buffer.push('Function ' + className + 'F(proxy, cb_poll)', '\tSet ' + className + 'F = (New ' + className + ')(proxy,cb_poll)')
    buffer.push('End Function')
    window['parseVB'](buffer.join('\r\n'))
    var re = window[className + 'F'](proxy, cb_poll)
    return re
}

上面只是核心代码示例,仅实现为根据配置返回一个可监听属性改变的VBScript对象。
要让vue实例可以执行原型vue原型链上的方法,还要将vue原型链上的方法复制到这个VBScript对象上。
Object.definePropertiesByVBS作出了改进实现了方法 Object.VBVueFactory 来生成vue实例(实际上是VBScript对象)

用这个修改版的vue.js实现了一个todolist,中间排查了若干问题,最终成功跑起来。

至此,完成兼容IE8的Vue.js,使用上与原版Vue.js并没有区别。耶!

abeet avatar Nov 28 '18 11:11 abeet

确实可以跑。厉害了

Pachulia avatar Feb 15 '19 09:02 Pachulia

Vue兼容IE的研究、实践

背景

公司前端框架使用的Vue.js,因为它基于Object.defineProperties来监听数据变化,而IE8是不支持Object.defineProperties的,所以注定不能兼容IE8。 Vue作者尤雨溪也无意让Vue支持IE8,见 vuejs/vuejs.org#50 目前Vue.js我们只用在后台界面,网站前台由于要兼容IE8,使用的是jQuery或Backbone,造成技术栈不统一,工程化不够。 为此想寻求Vue.js兼容IE8的方案。

可行性调研

github上搜索"vue IE8" 找到一个项目 https://github.com/wcflmy/VM4IE8 这个项目的数据监听是通过一个把一个model的数据,附加到一个dom元素上, 然后ie8下使用Object.defineProperty来监听dom上属性的改变 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty#Internet_Explorer_8_specific_notes 从Mozilla的资料上来看,有两大问题, 一个问题:因为model是附在dom上的,会有很多的dom属性,不能再用for遍历, 二个问题:数据键名要避开所有的dom属性名, 综上:基本等于没有用

另外一个未完成的项目,如 https://github.com/wusfen/vm https://github.com/wusfen/vue-ie 发邮件问了作者,他用的类似Angular的检测机制,对所有的异步的函数、方法,进行复写、注入 $foceUpdate ,如 ajax, setTimeout, Image.onload, ... ,基本覆盖常用情况。 不太喜欢Angular里面的zone.js对各种异步函数的污染,在实践中有遇到过因为Promise被污染导致某些库不正常(axios?)的情况。

https://www.cnblogs.com/rubylouvre/p/3598133.html 感觉还是Avalon里用到的VBScript方案更靠谱一些 要注意的是 value作为VBScript的特殊值也不能使用作属性名。

在网上还有一个模板解析器用到了VBScript来作defineProperties数据检测 https://github.com/purplebamboo/pat/blob/master/build/latest/pat.js 应该是有借鉴Avalon

至此定下方向,使用VBScript实现setter/getter来代替Object.defineProperties来监听数据变化

在github上再搜索了一些VBScript实现setter/getter的资料 http://webreflection.blogspot.com/2011/03/rewind-getters-setters-for-all-ie-with.html https://gist.github.com/jeffrafter/189354 有人提到在dojo里就用了VBScript实现setter/getter, 由此看来这不是Avalon的首创。只是很奇怪dojo把VBScript在iframe里去执行,为什么不用exeScript? https://download.dojotoolkit.org/release-1.9.7/dojo-release-1.9.7/dojox/lang/observable.js.uncompressed.js

实现过程

由于Vue1的代码逻辑比Vue2更直观,并且没有使用虚拟dom,有利于debug,所以我计划先实现Vue1兼容IE8,下次再实现Vue2兼容IE8。

VBScript实现setter/getter测试

将Avalon中的 createViewModel 抽取出来, 在IE8下写了一个页面测试双向绑定,成功!

将Pat.js中的define方法抽取出来, 在IE8下写了一个页面测试双向绑定成功,成功!

先解决vue1在IE8下的语法错误

然后是vue在不管双向绑定的情况下,在ie8下跑起来, 各种要shim的方法, 包括但不限于以下

  • IE8下Array实例没有forEach等方法
  • IE8下String实例没有trim等方法
  • IE8下Function实例没有bind等方法
  • IE8下Object没有keys等方法
  • 在测试中发现IE8下dom元素的cloneNode方法有bug,多个text子节点只clone了一个
  • IE8下input元素没有change事件 当然还有最重要的一个,IE8下没有Object.defineProperties

先找一个es5-shim 把数组等基本对象的成员方法补齐。 https://github.com/es-shims/es5-shim

由于一般的es6-shim库,代码量大,而且对Object.defineProperties的实现根本不实用,所以vue1中涉及的es6特有的方法,我要一个个补齐,而不再引用某个库。 先找了 es5 shim es6 shim 参考 https://github.com/seamus-oconnor/lift-js/tree/master/src/modules/es5/object https://github.com/paulmillr/es6-shim/blob/master/es6-shim.js 补齐了以下几个方法/属性

Object.freeze //无法实现,仅不抛错
Object.isExtensible //无法实现,仅不抛错
Object.create
Object.getOwnPropertyNames
Object.assign
Element.prototype.addEventListener
Element.prototype.removeEventListener
Event.prototype.target
Event.prototype.stopPropagation
Event.prototype.preventDefault
Object.defineProperties // 对于非dom元素,还需要调用 Object.definePropertiesByVBS才返回有监听属性改变的VBScript对象
Object.defineProperty  // 对于非dom元素,还需要调用 Object.definePropertiesByVBS才返回有监听属性改变的VBScript对象

至此vue.js在IE8下载入时不再抛出脚本错误

最关键的一步,实现Object.definePropertiesByVBS

上面的实现过程中,Object.definePropertyObject.defineProperties被实现为仅配置一个js对象有哪些属性要被监听,调用 Object.definePropertiesByVBS 后,会返回一个新的VBScript对象,这个对象上会监听属性的改变。 注意:是新的VBScript对象,不是原来的js对象 Object.definePropertiesByVBS的大体逻辑如下

window.execScript(['Function parseVB(code)', '\tExecuteGlobal(code)', 'End Function'].join('\r\n'), 'VBScript')
Object.defineProperty = function (obj, prop, desc) {
  obj.__defindeProperties__[prop] = desc
}
var VB_ID = 0
Object.definePropertiesByVBS = function (obj) {
    var cb_poll = {}
    var className = 'VB' + VB_ID++
    buffer.push('Class ' + className)
    for (var key in obj.__defindeProperties__) {
      var desc = obj.__defindeProperties__[key]
      cb_poll[key + '_set'] = desc.set
      buffer.push(
        '\tPublic Property Let [' + key + '](value)',
        '\t\tCall [__proxy](me, "set", "' + key + '", value)',
        '\tEnd Property',
        '\tPublic Property Set [' + key + '](value)',
        '\t\tCall [__proxy](me, "set", "' + key + '", value)',
        '\tEnd Property'
      )
      cb_poll[key + '_get'] = desc.get
      buffer.push(
        '\tPublic Property Get [' + key + ']',
        '\tOn Error Resume Next', 
        '\t\tSet [' + key + '] = [__proxy](me, "get", "' + key + '")',
        '\tIf Err.Number <> 0 Then',
        '\t\t[' + key + '] = [__proxy](me, "get", "' + key + '")',
        '\tEnd If',
        '\tOn Error Goto 0',
        '\tEnd Property'
      )
    }
    buffer.push('End Class')
    buffer.push('Function ' + className + 'F(proxy, cb_poll)', '\tSet ' + className + 'F = (New ' + className + ')(proxy,cb_poll)')
    buffer.push('End Function')
    window['parseVB'](buffer.join('\r\n'))
    var re = window[className + 'F'](proxy, cb_poll)
    return re
}

上面只是核心代码示例,仅实现为根据配置返回一个可监听属性改变的VBScript对象。 要让vue实例可以执行原型vue原型链上的方法,还要将vue原型链上的方法复制到这个VBScript对象上。 对Object.definePropertiesByVBS作出了改进实现了方法 Object.VBVueFactory 来生成vue实例(实际上是VBScript对象)

用这个修改版的vue.js实现了一个todolist,中间排查了若干问题,最终成功跑起来。

至此,完成兼容IE8的Vue.js,使用上与原版Vue.js并没有区别。耶!

对于 vue2.x 有实现的思路吗?

smalike avatar Jul 09 '19 07:07 smalike

厉害了, 赞一个

muzud avatar Mar 24 '20 15:03 muzud

有完整的代码么?

QJesus avatar Mar 29 '20 01:03 QJesus

@QJesus https://github.com/abeet/vue1-ie

hlizard avatar Aug 19 '21 11:08 hlizard