Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vue兼容IE的研究、实现 #35

Open
abeet opened this issue Nov 28, 2018 · 5 comments
Open

Vue兼容IE的研究、实现 #35

abeet opened this issue Nov 28, 2018 · 5 comments

Comments

@abeet
Copy link
Owner

abeet commented Nov 28, 2018

Vue兼容IE的研究、实践

背景

公司前端框架使用的Vue.js,因为它基于Object.defineProperties来监听数据变化,而IE8是不支持Object.defineProperties的,所以注定不能兼容IE8。
Vue作者尤雨溪也无意让Vue支持IE8,见
vuejs/v2.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并没有区别。耶!

@Pachulia
Copy link

确实可以跑。厉害了

@smalike
Copy link

smalike commented Jul 9, 2019

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 有实现的思路吗?

@muzud
Copy link

muzud commented Mar 24, 2020

厉害了, 赞一个

@QJesus
Copy link

QJesus commented Mar 29, 2020

有完整的代码么?

@hlizard
Copy link

hlizard commented Aug 19, 2021

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants