理想是丰满的,现实是骨感的,react早期的版本虽然号称支持IE8,但是页面总会不自觉切换到奇异模式下,导致报错。因此必须让react连IE6,7都支持,这才是最安全。但React本身并不支持IE6,7,因此anu使有用武之地了。

https://github.com/RubyLouvre/anu

但光是anu不行,兼容IE是一个系统性的工程,涉及到打包压缩,各种polyfill垫片。

首先说一下anu如何支持低版本浏览器。anu本身没有用到太高级的API,像Object.defineProperty, Object.seal, Object.freeze, Proxy, WeakMap等无法 模拟的新API,anu一个也没有用,而const, let, 箭头函数,es6模块,通过babel编译就可以搞定了。

而框架用到的一些es5,es6方法,我已经提供了一个叫polyfill的文件为大家准备好,大家也可以使用bable.polyfill实现兼容。

  1. Array.prototype.forEach
  2. Function.prototype.bind
  3. JSON
  4. window.console
  5. Object.keys
  6. Object.is
  7. Object.assign
  8. Array.isArray

https://github.com/RubyLouvre/anu/blob/master/dist/polyfill.js

剩下就是事件系统的兼容。React为了实现一个全能的事件系统,3万行的react-dom,有一半是在搞事件的。事件系统之所以这么难写,是因为React要实现整个标准事件流,从捕获阶段到target阶段再到冒泡阶段。如果能获取事件源对象到document这一路经过的所有元素,就能实现事件流了。但是在IE下,只有冒泡阶段,并且许多重要的表单事件不支持冒泡到document。为了事件冒泡,自jQuery时代起,前端高手们已经摸索出一套方案了。使用另一个相似的事件来伪装不冒泡事件,冒泡到document后,然后变成原来的事件触发对应的事件。

比如说IE下,使用focusin冒充focus, focusout冒充blur。chrome下,则通过addEventListener的第三个参加为true,强制让focus, blur被document捕获到。

//Ie6-9
if(msie < 9){
eventHooks.onFocus = function(dom) {
addEvent(dom, "focusin", function(e) {
addEvent.fire(dom, "focus");
});
};
eventHooks.onBlur = function(dom) {
addEvent(dom, "blurout", function(e) {
addEvent.fire(dom, "blur");
});
};
}else{
eventHooks.onFocus = function(dom) {
addEvent(
dom,
"focus",
function(e) {
addEvent.fire(dom, "focus");
},
true
);
};
eventHooks.onBlur = function(dom) {
addEvent(
dom,
"blur",
function(e) {
addEvent.fire(dom, "blur");
},
true
);
};
}

低版本的oninput, onchange事件是一个麻烦,它们最多冒泡到form元素上。并且IE也没有oninput,只有一个相似的onpropertychange事件。IE9,IE10的oninput其实也有许多BUG,但大家要求放低些,我们也不用理会IE9,IE10的oninput事件。IE6-8的oninput事件,我们是直接在元素上绑定onpropertychange事件,然后触发一个datasetchanged 事件冒泡到document上,并且这个datasetchanged事件对象带有一个__type__属性,用来说明它原先冒充的事件。

function fixIEInput(dom, name) {
addEvent(dom, "propertychange", function(e) {
if (e.propertyName === "value") {
addEvent.fire(dom, "input");
}
});
} addEvent.fire = function dispatchIEEvent(dom, type, obj) {
try {
var hackEvent = document.createEventObject();
if (obj) {
Object.assign(hackEvent, obj);
}
hackEvent.__type__ = type;
//IE6-8触发事件必须保证在DOM树中,否则报"SCRIPT16389: 未指明的错误"
dom.fireEvent("ondatasetchanged", hackEvent);
} catch (e) {}
}; function dispatchEvent(e) {//document上绑定的事件派发器
var __type__ = e.__type__ || e.type;
e = new SyntheticEvent(e);
var target = e.target;
var paths = [];//获取整个冒泡的路径
do {
var events = target.__events;
if (events) {
paths.push({ dom: target, props: events });
}
} while ((target = target.parentNode) && target.nodeType === 1);
// ...略
}

addEvent.fire这个方法在不同浏览器的实现是不一样的,这里显示的IE6-8的版本,IE9及标准浏览器是使用document.createEvent, initEvent, dispatchEvent等API来创建事件对象与触发事件。在IE6-8中,则需要用document.createEventObject创建事件对象,fireEvent来触发事件。

ondatasetchanged事件是IE一个非常偏门的事件,因为IE的 fireEvent只能触发它官网上列举的几十个事件,不能触发自定义事件。而ondatasetchanged事件在IE9,chrome, firefox等浏览器中是当成一个自定义事件来对待,但那时它是使用elem.dispatchEvent来触发了。ondatasetchanged是一个能冒泡的事件,只是充作信使,将我们要修改的属性带到document上。

此是其一,onchange事件也要通过ondatasetchanged来冒充,因为IE下它也不能冒泡到document。onchange事件在IE还是有许多BUG(或叫差异点)。checkbox, radio的onchange事件必须在失去焦点时才触发,因此我们在内部用onclick来触发,而select元素在单选时候下,用户选中了某个option, select.value会变成option的value值,但在IE6-8下它竟然不会发生改变。最绝的是select元素也不让你修改value值,后来我奠出修改HTMLSelectElement原型链的大招搞定它。

  try {
Object.defineProperty(HTMLSelectElement.prototype, "value", {
set: function(v) {
this._fixIEValue = v;
},
get: function() {
return this._fixIEValue;
}
});
} catch (e) {} function fixIEChange(dom, name) {
//IE6-8, radio, checkbox的点击事件必须在失去焦点时才触发
var eventType = dom.type === "radio" || dom.type === "checkbox"
? "click"
: "change";
addEvent(dom, eventType, function(e) {
if (dom.type === "select-one") {
var idx = dom.selectedIndex,
option,
attr;
if (idx > -1) {
//IE 下select.value不会改变
option = dom.options[idx];
attr = option.attributes.value;
dom.value = attr && attr.specified ? option.value : option.text;
}
}
addEvent.fire(dom, "change");
});
}

此外,滚动事件的兼容性也非常多,但在React官网中,统一大家用onWheel接口来调用,在内部实现则需要我们根据浏览器分别用onmousewheel, onwheel, DOMMouseScroll来模拟了。

当然还有很多很多细节,这里就不一一列举了。为了防止像React那样代码膨胀,针对旧版本的事件兼容,我都移到ieEvent.js文件中。然后基于它,打包了一个专门针对旧版本IE的ReactIE

https://github.com/RubyLouvre/anu/tree/master/dist

大家也可以通过npm安装,1.0.2就拥有这个文件

npm install anujs

下面通过一个示例介绍如何使用ReactIE.

<!DOCTYPE html>
<html> <head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width"> <script src="./dist/polyfill.js"></script>
<script src="./dist/ReactIE.js"></script>
<script src="./dist/index9.js"></script> </head> <body> <div>这个默认会被清掉</div>
<div id='example'></div> </body> </html>

首先建立一个页面,里面有三个JS,其实前两个文件也能单独打包的。

index.js的源码是这样的,业务线开发时是直接上JSX与es6,为了兼容IE6-8,请不要在业务代码上用Object.defineProperty与Proxy

class Select extends React.Component{
constructor() {
super()
this.state = {
value: 'bbb'
}
this.onChange = this.onChange.bind(this)
}
onChange(e){
console.log(e.target.value)
this.setState({
value: e.target.value
})
}
render() {
return <div><select value={this.state.value} onChange={this.onChange}>
<option value='aaa'>aaa</option>
<option value='bbb'>bbb</option>
<option value='ccc'>ccc</option>
</select><p>{this.state.value}</p></div>
}
}
class Input extends React.Component{
constructor() {
super()
this.state = {
value: 'input'
}
this.onInput = this.onInput.bind(this)
}
onInput(e){
this.setState({
value: e.target.value
})
}
render() {
return <div><input value={this.state.value} onInput={this.onInput} />{this.state.value}</div>
}
}
class Radio extends React.Component{
constructor(props) {
super(props)
this.state = {
value: this.props.value
}
this.onChange = this.onChange.bind(this)
}
onChange(e){
console.log(e.target.value)
this.setState({
value: e.target.value
})
}
render() {
return <span><input type='radio' name={this.props.name} value={this.props.value} onChange={this.onChange} />{this.state.value+''}</span>
}
}
class Playground extends React.Component{
constructor(props) {
super(props)
this.state = {
value: '请上下滚动鼠标滚轮'
}
this.onWheel = this.onWheel.bind(this)
}
onWheel(e){
this.setState({
value: e.wheelDelta
})
}
render() {
return <div style={{width:300,height:300,backgroundColor:'red',display:'inline-block'}} onWheel={this.onWheel} >{this.state.value}</div>
}
}
class MouseMove extends React.Component{
constructor(props) {
super(props)
this.state = {
value: '请在绿色区域移动'
}
this.onMouseMove = this.onMouseMove.bind(this)
}
onMouseMove(e){
var v = e.pageX+' '+e.pageY;
this.setState({
value: v
})
}
render() {
return <div style={{width:300,height:300,backgroundColor:'#a9ea00',display:'inline-block'}} onMouseMove={this.onMouseMove} >{this.state.value}</div>
}
}
class FocusEl extends React.Component{
constructor(props) {
super(props)
this.state = {
value: '点我'
}
this.onFocus = this.onFocus.bind(this)
}
onFocus(e){
console.log(e.target.title)
}
render() {
return <input title={this.props.title} onKeyUp={(e)=>{console.log(e.which)}} style={{width:100,height:50,backgroundColor:'green',display:'inline-block'}} onFocus={this.onFocus} />
}
}
window.onload = function(){
window.s = ReactDOM.render( <div><Select /><Input /><Radio name='sex' value="男" /><Radio name='sex' value='女'/>
<p><Playground /> <MouseMove /><FocusEl title="aaa" /><FocusEl title="bbb" /></p> </div>, document.getElementById('example'))
}

然后我们建一个webpack.config.js,用的是webpack1

const webpack = require("webpack");
const path = require("path");
const fs = require("fs");
var es3ifyPlugin = require('es3ify-webpack-plugin'); module.exports = {
context: __dirname,
entry: {
index9: "./src/index9.js"
},
output: {
path: __dirname + "/dist/",
filename: "[name].js"
},
plugins: [new es3ifyPlugin()],
module: {
loaders: [
{
test: /\.jsx?$/,
loader: "babel-loader",
exclude: path.resolve(__dirname, "node_modules")
}
]
}, resolve: {
//如果不使用anu,就可以把这里注释掉
alias: {
react: "anujs/dist/ReactIE.js",
"react-dom": "anujs/dist/ReactIE.js"
}
}
};

es3ify-webpack-plugin是专门将es5代码转换为es3代码,因为es5是允许用关键字,保留字作为对象的方法与属性,而es3不能。万一碰上module.default,我们就坑大了。es3ify是一个利器。

babel是通过.babelrc来配置,里面用到一个

 {
"presets": [
["es2015", { "modules": false }], "react"
],
"plugins": [
[
"transform-es2015-classes", {
"loose": true
}
]
]
}

babel-plugin-transform-es2015-classes记使用loose模式。

babel-preset-es2015后面这样设置是禁用生成"use strict",也建议直接换成babel-preset-avalon,这是个preset生成的代码兼容性更好。

如果大家用 uglify-js进行代码上线,这也要注意一下,这里有许多坑,它默认会把es3ify干的活全部白做了。详见 https://github.com/zuojj/fedlab/issues/5 这篇文章

new webpack.optimize.UglifyJsPlugin({
compress: {
properties: false,
warnings: false
},
output: {
beautify: true,
quote_keys: true
},
mangle: {
screw_ie8: false
},
sourceMap: false
})

最后大家可以通过加Q 79641290 联系我。

高性能迷你React框架anu在低版本IE的实践的更多相关文章

  1. 发布高性能迷你React框架anu

    anu, 读作[安努],原意为苏美尔的主神. anu是我继avalon之后又一个新框架(github仓库为https://github.com/RubyLouvre/anu, 欢迎加星与试用) 此框架 ...

  2. 高性能迷你React框架anujs1.1.0发布

    本版本对setState与forceUpdate内部依赖的setStateImpl进行了重构,性能稳定在60pfs之上.并且将组件实例的所有内部方法与属性都改成以___开头. https://gith ...

  3. 高性能迷你React框架anujs1.0.8发布

    本版本由于得到业务线同学的大力支持,掀出许多问题,因此改进地方良多,为anujs在完美替换React的道路上前进了不少.现在anujs经测试可以运行于IE7中.至少怎么做可以参看官网,https:// ...

  4. 高性能迷你React框架anujs1.1.3发布

    anujs现在只差一个组件(mention)就完全支持阿里的antd UI库了.一共跑通346个测试, 应该是全世界最接近官方React的迷你框架了. 以后的工作就是把React16的一些新特性支持了 ...

  5. 高性能迷你React框架anujs1.0.5发布

    实现对createFactory的支持,优化scheduler与dispose机制,提供ReactShim文件,跑通公司内部4套测试 npm i anujs 或者使用架手架 https://githu ...

  6. React+Webpack+ES6 兼容低版本浏览器(IE9)解决方案

    虽然过了兼容IE6的噩梦时代,IE依旧阴魂不散,因为你可能还要兼容IE9.在ES6已经普及的今天,用ES6写react已经成了标配.但是babel编译的js语法,由于某些不规范的写法,可能在IE9下不 ...

  7. ES6 + Webpack + React + Babel 如何在低版本浏览器上愉快的玩耍

    https://blog.csdn.net/a324539017/article/details/52824189

  8. client高性能组件化框架React简单介绍、特点、环境搭建及经常使用语法

    [本文源址:http://blog.csdn.net/q1056843325/article/details/54729657 转载请加入该地址] 明天就是除夕了 预祝大家新春快乐 [ ]~( ̄▽ ̄) ...

  9. Ant Design of React 框架使用总结1

    一.  为什么要用UI 框架 统一了样式交互动画 . Ui框架会对样式,交互动画进行统一,保证了系统风格完整统一,不像拼凑起来的. 兼容性 ,不是去兼容IE 6 7 8那些低版本浏览器,而是对主流的标 ...

随机推荐

  1. JS作用域相关知识(#精)

    在学习<你不知道的JS>一书中,特将作用域相关知识在此分享一下: #说到作用域,就不得不提到LHS查询和RHS查询: 1)如果查询目的是对变量进行赋值,则使用LHS查询 2)如果查询目的是 ...

  2. 设备像素,设备独立像素,CSS像素

    之前学了移动端的开发对设备像素.设备独立像素.CSS像素弄得不太清楚,所以趁周末的时间查了一下,稍加整理 一些概念 在进行具体的分析之前,首先得知道下面这些关键性基本概念. CSS像素 CSS像素是W ...

  3. java类集框架(ArrayList,LinkedList,Vector区别)

    主要分两个接口:collection和Map 主要分三类:集合(set).列表(List).映射(Map)1.集合:没有重复对象,没有特定排序方式2.列表:对象按索引位置排序,可以有重复对象3.映射: ...

  4. Redisson入门

    Redisson入门 Author:Ricky  Date:2017-04-24 Redisson概述 Redisson是架设在Redis基础上的一个Java驻内存数据网格(In-Memory Dat ...

  5. 界面底部Tab实现

    现在基本上大部分的手机APP都要实现底部Tab,底部实现Tab的实现方式有很多种,那么有没有好的实现方式呢? 今天我将使用一个开源插件来实现底部Tab 参考自zhangli_的博客:http://bl ...

  6. 七牛整合 ueditor (拦住那头牛,七牛又如何)

    最近遇到个项目,要求所有图片都必须整合到七牛上,看了把你谈文档踩在前辈们的基础上终于把他完成了,恰巧本屌丝最近刚好有时间,本着天下屌丝是一家的原则,和小朋友们一同学习 闲话少说入正题. 第一 :下载编 ...

  7. crontab定时任务不执行的原因

    1.重启crontab若是遇见"You (cloudlogin) are not allowed to use this program (crontab)                 ...

  8. PDO(数据访问抽象层)、pdo事务功能和预处理功能---2017-05-05

    之前所学的数据访问都是用mysqli做成类来访问的,但是mysqli这个类只是针对mysql这个数据库的:那么如果访问其他类型的数据库呢? 那么这就用到了PDO(数据访问抽象层). 一.关于PDO基本 ...

  9. 阿里云主机试用之自建站点和ftp上传所遇的2个问题

    1.Access to the requested object is only available from the local network 其实我并没有自建站点,只是使用了XAMPP来建了ap ...

  10. 0基础搭建Hadoop大数据处理-环境

    由于Hadoop需要运行在Linux环境中,而且是分布式的,因此个人学习只能装虚拟机,本文都以VMware Workstation为准,安装CentOS7,具体的安装此处不作过多介绍,只作需要用到的知 ...