宗是那么wwWb435不尽人意 英文的,难不成还能是b435cOm更该了

IMWeb前端社区IMWeb前端社区揭秘腾讯前端思想,收获最新前沿资讯关注专栏更多最新文章{&debug&:false,&apiRoot&:&&,&paySDK&:&https:\u002F\u002Fpay.zhihu.com\u002Fapi\u002Fjs&,&wechatConfigAPI&:&\u002Fapi\u002Fwechat\u002Fjssdkconfig&,&name&:&production&,&instance&:&column&,&tokens&:{&X-XSRF-TOKEN&:null,&X-UDID&:null,&Authorization&:&oauth c3cef7c66aa9e6a1e3160e20&}}{&database&:{&Post&:{&&:{&title&:&鹅厂原创 | 可能是史上最全的weex踩坑攻略&,&author&:&imwebqian-duan-xue-yuan&,&content&:&\u003Cblockquote\u003E作者:applecatkay \u003Cbr\u003E原文:\u003Ca href=\&http:\u002F\u002Fimweb.io\u002Ftopic\u002F59355fdec99e696d3ab4d916\&\u003E可能是史上最全的weex踩坑攻略 - 腾讯Web前端 IMWeb 团队社区\u003C\u002Fa\u003E\u003C\u002Fblockquote\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E& 这是一篇有故事的文章 --- 来自一个weex在生产环境中相爱相杀的小码畜..\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Ch2\u003E\u003Cb\u003E故事一: Build\u003C\u002Fb\u003E\u003C\u002Fh2\u003E\u003Cp\u003E虽然weex的口号是一次撰写,多端运行, 但其实build环节是有差异的, native端构建需要使用weex-loader, 而web端则是使用vue-loader,除此以外还有不少差异点, 所以webpack需要两套配置。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003E最佳实践\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003E使用webpack生成两套bundle,一套是基于vue-router的web spa, 另一套是native端的多入口的bundlejs\u003C\u002Fp\u003E\u003Cp\u003E首先假设我们在src\u002Fviews下开发了一堆页面\u003C\u002Fp\u003E\u003Cimg src=\&v2-3ecfa476bfbd341e37a11.png\& data-rawwidth=\&193\& data-rawheight=\&196\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003Ebuild web配置\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003Eweb端的入口文件有 render.js:\u003C\u002Fp\u003E\u003Ccode lang=\&js\&\u003Eimport weexVueRenderer from 'weex-vue-render'\nVue.use(weexVueRenderer)\u003C\u002Fcode\u003E\u003Cp\u003Emain.js:\u003C\u002Fp\u003E\u003Ccode lang=\&js\&\u003Eimport App from '.\u002FApp.vue'\nimport VueRouter from 'vue-router'\nimport routes from '.\u002Froutes'\nVue.use(VueRouter)\nvar router = new VueRouter({\n
routes\n})\n\u002F* eslint-disable no-new *\u002F\nnew Vue({\n
el: '#root',\n
render: h =& h(App)\n})\n\nrouter.push('\u002F')\u003C\u002Fcode\u003E\u003Cp\u003EApp.vue\u003C\u002Fp\u003E\u003Ccode lang=\&js\&\u003E&template&\n
&transition name=\&fade\& mode=\&out-in\&&\n
&router-view class=\&.container\& \u002F&\n
&\u002Ftransition&\n&\u002Ftemplate&\n&script&\nexport default {\n
\u002F\u002F ...\n}\n&\u002Fscript&\n&style&\n\u002F\u002F ...\n&\u002Fstyle&\u003C\u002Fcode\u003E\u003Cp\u003Ewebpack.prod.conf.js入口:\u003C\u002Fp\u003E\u003Ccode lang=\&js\&\u003Econst webConfig = merge(getConfig('vue'), {\n
entry: {\n
app: ['.\u002Fsrc\u002Frender.js', '.\u002Fsrc\u002Fapp.js']\n
output: {\n
path: path.resolve(distpath, '.\u002Fweb'),\n
filename: 'js\u002F[name].[chunkhash].js',\n
chunkFilename: 'js\u002F[id].[chunkhash].js'\n
...\n module: {\n
rules: [\n
test: \u002F\\.vue$\u002F,\n
loader: 'vue-loader'\n
]\n }\n})\u003C\u002Fcode\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003Ebuild native配置\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003Enative端的打包流程其实就是将src\u002Fviews下的每个.vue文件导出为一个个单独的vue实例, 写一个node脚本即可以实现。\u003C\u002Fp\u003E\u003Ccode lang=\&js\&\u003E\u002F\u002F build-entry.js\nrequire('shelljs\u002Fglobal')\nconst path = require('path')\nconst fs = require('fs-extra')\n\nconst srcPath = path.resolve(__dirname, '..\u002Fsrc\u002Fviews') \u002F\u002F 每个.vue页面\nconst entryPath = path.resolve(__dirname, '..\u002Fentry\u002F') \u002F\u002F 存放入口文件的文件夹\nconst FILE_TYPE = '.vue'\n\nconst getEntryFileContent = path =& {\n
return `\u002F\u002F 入口文件\nimport App from '${path}${FILE_TYPE}'\n\u002F* eslint-disable no-new *\u002F\nnew Vue({\n
el: '#root',\n
render: h =& h(App)\n})\n\n
`\n}\n\u002F\u002F 导出方法\nmodule.exports = _ =& {\n
\u002F\u002F 删除原目录\n
rm('-rf', entryPath)\n
\u002F\u002F 写入每个文件的入口文件\n
fs.readdirSync(srcPath).forEach(file =& {\n
const fullpath = path.resolve(srcPath, file)\n
const extname = path.extname(fullpath)\n
const name = path.basename(file, extname)\n
if (fs.statSync(fullpath).isFile() && extname === FILE_TYPE) {\n
\u002F\u002F写入vue渲染实例\n
fs.outputFileSync(path.resolve(entryPath, name + '.js'), getEntryFileContent('..\u002Fsrc\u002Fviews\u002F' + name))\n
const entry = {}\n
\u002F\u002F 放入多个entry\n
fs.readdirSync(entryPath).forEach(file =& {\n
const name = path.basename(file, path.extname(path.resolve(entryPath, file)))\n
entry[name] = path.resolve(entryPath, name + '.js')\n
return entry\n}\u003C\u002Fcode\u003E\u003Cp\u003Ewebpack.build.conf.js中生成并打包多入口:\u003C\u002Fp\u003E\u003Ccode lang=\&js\&\u003Econst buildEntry = require('.\u002Fbuild_entry')\n\u002F\u002F ..\n\u002F\u002F weex配置\nconst weexConfig = merge(getConfig('weex'), {\n
entry: buildEntry(), \u002F\u002F 写入多入口\n
output: {\n
path: path.resolve(distPath, '.\u002Fweex'),\n
filename: 'js\u002F[name].js' \u002F\u002F weex环境无需使用hash名字\n
module: {\n
rules: [\n
test: \u002F\\.vue$\u002F,\n
loader: 'weex-loader'\n
}\n})\n\nmodule.exports = [webConfig, weexConfig]\u003C\u002Fcode\u003E\u003Cp\u003E最终效果:\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-cda8b6ac46c43987e1ed.png\& data-rawwidth=\&1206\& data-rawheight=\&110\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Ch2\u003E\u003Cb\u003E故事二: 使用预处理器\u003C\u002Fb\u003E\u003C\u002Fh2\u003E\u003Cp\u003E在vue单文件中, 我们可以通过在vue-loader中配置预处理器, 代码如下:\u003C\u002Fp\u003E\u003Ccode lang=\&js\&\u003E{\n
test: \u002F\\.vue$\u002F,\n
loader: 'vue-loader',\n
options: {\n
loaders: {\n
scss: 'vue-style-loader!css-loader!sass-loader', \u002F\u002F &style lang=\&scss\&&\n
sass: 'vue-style-loader!css-loader!sass-loader?indentedSyntax' \u002F\u002F &style lang=\&sass\&&\n
}\n}\u003C\u002Fcode\u003E\u003Cp\u003E而weex在native环境下其实将css处理成json加载到模块中, 所以...\u003C\u002Fp\u003E\u003Cul\u003E\u003Cli\u003E使用vue-loader配置的预处理器在web环境下正常显示, 在native中是无效的。\u003C\u002Fli\u003E\u003Cli\u003Enative环境下不存在全局样式, 在js文件中import 'index.css'也是无效的。\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cimg src=\&v2-f19cd77fa55ec8d302cfdb532d266d4a.png\& data-rawwidth=\&352\& data-rawheight=\&196\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003E解决问题一\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003E研究weex-loader源码后发现在.vue中是无需显示配置loader的, 只需要指定&style lang=\&stylus\&&并且安装stylus stylus-loader即可,weex-loader会根据lang去寻找对应的loader. 但因为scss使用sass-loader, 会报出scss-loader not found, 但因为sass默认会解析scss语法, 所以直接设置lang=\&sass\&是可以写scss语法的, 但是ide就没有语法高亮了. 可以使用如下的写法:\u003C\u002Fp\u003E\u003Ccode lang=\&js\&\u003E&style lang=\&sass\&&\n
@import '.\u002Findex.scss'\n&\u002Fstyle&\u003C\u002Fcode\u003E\u003Cp\u003E语法高亮, 完美!\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003E解决问题二\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003E虽然没有全局样式的概念, 但是支持单独import样式文件。\u003C\u002Fp\u003E\u003Ccode lang=\&js\&\u003E&style lang=\&sass\&&\n
@import '.\u002Fcommon.scss'\n
@import '.\u002Fvariables.scss'\n
\u002F\u002F ...\n&\u002Fstyle&\u003C\u002Fcode\u003E\u003Ch2\u003E\u003Cb\u003E故事三: 样式差异\u003C\u002Fb\u003E\u003C\u002Fh2\u003E\u003Cp\u003E这方面官方文档已经有比较详细的描述, 但还是有几点值得注意的。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003E简写\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003Eweex中的样式不支持简写, 所有类似margin: 0 0 10px 10px的都是\u003Ci\u003E不支持\u003C\u002Fi\u003E的。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003E背景色\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003Eandroid下的view是有白色的默认颜色的, 而iOS如果不设置是没有默认颜色的, 这点需要注意。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003E浮点数误差\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003Eweex默认使用750px * 1334px作为适配尺寸, 实际渲染时由于浮点数的误差可能会存在几px的误差, 出现细线等样式问题, 可以通过加减几个px来调试。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003E嵌套写法\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003E即使使用了预处理器, css嵌套的写法也是会导致\u003Ci\u003E样式失效\u003C\u002Fi\u003E的。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Ch2\u003E\u003Cb\u003E故事四: 页面跳转\u003C\u002Fb\u003E\u003C\u002Fh2\u003E\u003Cp\u003Eweex下的页面跳转有三种形式:\u003C\u002Fp\u003E\u003Cul\u003E\u003Cli\u003Enative -& weex: weex页面需要一个控制器作为容器, 此时就是native间的跳转。\u003C\u002Fli\u003E\u003Cli\u003Eweex -& native: 需要通过module形式通过发送事件到native来实现跳转。\u003C\u002Fli\u003E\u003Cli\u003Eweex -& weex: 使用navigator模块, 假设两个weex页面分别为a.js, b.js, 可以定义mixin方法。\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Ccode lang=\&js\&\u003E function isWeex () {\n
return process.env.COMPILE_ENV === 'weex' \u002F\u002F 需要在webpack中自定义\n
export default {\n\n
methods: {\n\n
push (path) {\n
if (isWeex()) {\n
const toUrl = weex.config.bundleUrl.split('\u002F').slice(0, -1).join('\u002F') + '\u002F' + path + '.js' \u002F\u002F 将a.js的绝对地址转为b.js的绝对地址\n
weex.requireModule('navigator').push({\n
url: toUrl,\n
animated: 'true'\n
} else {\n
this.$router.push(path) \u002F\u002F 使用vue-router\n
pop () {\n
if (isWeex()) {\n
weex.requireModule('navigator').pop({\n
animated: 'true'\n
} else {\n
window.history.back()\n
}\u003C\u002Fcode\u003E\u003Cul\u003E\u003Cli\u003E这样就组件里使用this.push(url), this.pop()来跳转。\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003E跳转配置\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cul\u003E\u003Cli\u003EiOS下页面跳转无需配置, 而android是需要的, 使用weexpack platform add android生成的项目是已配置的, 但官方的文档里并没有对于已存在的应用如何接入进行说明。\u003C\u002Fli\u003E\u003Cli\u003E其实android中是通过intent-filter来拦截跳转的。\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Ccode lang=\&js\&\u003E
&activity\n
android:name=\&.WXPageActivity\&\n
android:label=\&@string\u002Fapp_name\&\n
android:screenOrientation=\&portrait\&\n
android:theme=\&@android:style\u002FTheme.NoTitleBar\&&\n
&intent-filter&\n
&action android:name=\&android.intent.action.VIEW\&\u002F&\n
&action android:name=\&com.alibaba.weex.protocol.openurl\&\u002F&\n\n
&category android:name=\&android.intent.category.DEFAULT\&\u002F&\n
&category android:name=\&com.taobao.android.intent.category.WEEX\&\u002F&\n\n
&data android:scheme=\&http\&\u002F&\n
&data android:scheme=\&https\&\u002F&\n
&data android:scheme=\&file\&\u002F&\n
&\u002Fintent-filter&\n
&\u002Factivity&\u003C\u002Fcode\u003E\u003Cul\u003E\u003Cli\u003E然后我们新建一个WXPageActivity来代理所有weex页面的渲染, 核心的代码如下:\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Ccode lang=\&js\&\u003E [@Override](\u002Fuser\u002FOverride)\n
protected void onCreate(Bundle saveInstanceState) {\n
\u002F\u002F ...\n
Uri uri = getIntent().getData();\nBundle bundle = getIntent().getExtras();\n\n
if (uri != null) {\n
if (bundle != null) {\n
String bundleUrl = bundle.getString(\&bundleUrl\&);\n
if (!TextUtils.isEmpty(bundleUrl)) {\n
mUri = Uri.parse(bundleUrl);\n
if (mUri == null) {\n
Toast.makeText(this, \&the uri is empty!\&, Toast.LENGTH_SHORT).show();\n
finish();\\n
String path = mUri.toString();\n
\u002F\u002F 传来的url参数总会带上http:\u002F 应该是个bug 可以自己判断是否本地url再去跳转\n
String jsPath = path.indexOf(\&weex\u002Fjs\u002F\&) & 0 ? path.replace(\&http:\u002F\&, \&\&) :\n
HashMap&String, Object& options = new HashMap&String, Object&();\n
options.put(WXSDKInstance.BUNDLE_URL, jsPath);\n
mWXSDKInstance = new WXSDKInstance(this);\n
mWXSDKInstance.registerRenderListener(this);\n
mWXSDKInstance.render(\&weex\&, WXFileUtils.loadAsset(jsPath, this), options, null, -1, -1, WXRenderStrategy.APPEND_ASYNC);\n
}\u003C\u002Fcode\u003E\u003Cp\u003E\u003Cb\u003E来自\u003Ca href=\&https:\u002F\u002Fsegmentfault.com\u002Fu\u002Flizhiwodage\&\u003E@荔枝我大哥\u003C\u002Fa\u003E 的补充\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003E安卓和苹果方面可以在原生代码接管`navigator`这个模块,安卓方面只需要实现`IActivityNavBarSetter`,苹果方面好像是`WXNavigatorProtocol`,然后在app启动初始化weex时注册即可。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Ch2\u003E\u003Cb\u003E故事五: 页面间数据传递\u003C\u002Fb\u003E\u003C\u002Fh2\u003E\u003Cul\u003E\u003Cli\u003Enative -& weex: 可以在native端调用render时传入的option中自定义字段, 例如NSDictary *option = @{@\&params\&: @{}}, 在weex中使用weex.config.params取出数据。\u003C\u002Fli\u003E\u003Cli\u003Eweex -& weex: 使用storage。\u003C\u002Fli\u003E\u003Cli\u003Eweex -& native: 使用自定义module。\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Ch2\u003E\u003Cb\u003E故事六: 图片加载\u003C\u002Fb\u003E\u003C\u002Fh2\u003E\u003Cp\u003E官网有提到如何加载网络图片 但是加载本地图片的行为对于三端肯定是不一致的, 也就意味着我们得给native重新改一遍引用图片的路径再打包...\u003C\u002Fp\u003E\u003Cimg src=\&v2-ddfef1c16f0cc.png\& data-rawwidth=\&206\& data-rawheight=\&185\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E但是当然是有解决办法的啦。\u003C\u002Fp\u003E\u003Cimg src=\&v2-7bafeab478bf1d.png\& data-rawwidth=\&212\& data-rawheight=\&174\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cul\u003E\u003Cli\u003EStep 1 webpack设置将图片资源单独打包, 这个很easy, 此时bundleJs访问的图片路径就变成了\u002Fimages\u002F..\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Ccode lang=\&js\&\u003E
test: \u002F\\.(png|jpe?g|gif|svg)$\u002F,\n
loader: 'url-loader',\n
query: {\n
limit: 1,\n
name: 'images\u002F[hash:8].[name].[ext]'\n
}\u003C\u002Fcode\u003E\u003Cul\u003E\u003Cli\u003EStep 2 那么现在我们将\u003Ci\u003E同级目录下的js文件夹与images文件夹\u003C\u002Fi\u003E放入native中, iOS中一般放入mainBundle, Android一般放入src\u002Fmain\u002Fassets, 接下来只要在imgloader接口中扩展替换本地资源路径的代码就ok了。\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cp\u003EiOS代码如下:\u003C\u002Fp\u003E\u003Ccode lang=\&js\&\u003E- (id&WXImageOperationProtocol&)downloadImageWithURL:(NSString *)url imageFrame:(CGRect)imageFrame userInfo:(NSDictionary *)options completed:(void (^)(UIImage *, NSError *, BOOL))completedBlock{\n
if ([url hasPrefix:@\&\u002F\u002F\&]) {\n
url = [@\&http:\& stringByAppendingString:url];\n
\u002F\u002F 加载本地图片\n
if ([url hasPrefix:@\&file:\u002F\u002F\&]) {\n
NSString *newUrl = [url stringByReplacingOccurrencesOfString:@\&\u002Fimages\u002F\& withString:@\&\u002F\&];\n
UIImage *image = [UIImage imageNamed:[newUrl substringFromIndex:7]];\n
completedBlock(image, nil, YES);\n
return (id&WXImageOperationProtocol&)\n
} else {\n
\u002F\u002F 加载网络图片\n
return (id&WXImageOperationProtocol&)[[SDWebImageManager sharedManager]downloadImageWithURL:[NSURL URLWithString:url] options:0 progress:^(NSInteger receivedSize, NSInteger expectedSize) {\n
} completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {\n
if (completedBlock) {\n
completedBlock(image, error, finished);\n
}\n}\u003C\u002Fcode\u003E\u003Cp\u003EAndroid代码如下:\u003C\u002Fp\u003E\u003Ccode lang=\&js\&\u003E [@Override](\u002Fuser\u002FOverride)\n
public void setImage(final String url, final ImageView view,\n
WXImageQuality quality, final WXImageStrategy strategy) {\n\n
WXSDKManager.getInstance().postOnUiThread(new Runnable() {\n\n
[@Override](\u002Fuser\u002FOverride)\n
public void run() {\n
if(view==null||view.getLayoutParams()==null){\\n
if (TextUtils.isEmpty(url)) {\n
view.setImageBitmap(null);\\n
String temp =\n
if (url.startsWith(\&\u002F\u002F\&)) {\n
temp = \&http:\& +\n
if (temp.startsWith(\&\u002Fimages\u002F\&)) {\n
\u002F\u002F过滤掉所有相对位置\n
temp = temp.replace(\&..\u002F\&, \&\&);\n
temp = temp.replace(\&.\u002F\&, \&\&);\n
\u002F\u002F替换asset目录的配置\n
temp = temp.replace(\&\u002Fimages\u002F\&, \&file:\u002F\u002F\u002Fandroid_asset\u002Fweex\u002Fimages\u002F\&);\n
Log.d(\&ImageAdapter\&, \&url:\& + temp);\n
if (view.getLayoutParams().width &= 0 || view.getLayoutParams().height &= 0) {\\n
if(!TextUtils.isEmpty(strategy.placeHolder)){\n
Picasso.Builder builder=new Picasso.Builder(WXEnvironment.getApplication());\n
Picasso picasso=builder.build();\n
picasso.load(Uri.parse(strategy.placeHolder)).into(view);\n\n
view.setTag(strategy.placeHolder.hashCode(),picasso);\n
Picasso.with(WXEnvironment.getApplication())\n
.load(temp)\n
.into(view, new Callback() {\n
[@Override](\u002Fuser\u002FOverride)\n
public void onSuccess() {\n
if(strategy.getImageListener()!=null){\n
strategy.getImageListener().onImageFinish(url,view,true,null);\n
if(!TextUtils.isEmpty(strategy.placeHolder)){\n
((Picasso) view.getTag(strategy.placeHolder.hashCode())).cancelRequest(view);\n
[@Override](\u002Fuser\u002FOverride)\n
public void onError() {\n
if(strategy.getImageListener()!=null){\n
strategy.getImageListener().onImageFinish(url,view,false,null);\n
}\u003C\u002Fcode\u003E\u003Ch2\u003E\u003Cb\u003E故事七: 生产环境的实践\u003C\u002Fb\u003E\u003C\u002Fh2\u003E\u003Cp\u003E\u003Cb\u003E增量更新\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003E方案一\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003E可以使用google-diff-match-patch来实现, google-diff-match-patch拥有许多语言版本的实现, 思路如下:\u003C\u002Fp\u003E\u003Cul\u003E\u003Cli\u003E服务器端构建一套管理前端bundlejs的系统, 提供查询bundlejs版本与下载的api。\u003C\u002Fli\u003E\u003Cli\u003E客户端第一次访问weex页面时去服务端下载bundlejs文件。\u003C\u002Fli\u003E\u003Cli\u003E每次客户端初始化时静默访问服务器判断是否需要更新, 若需更新, 服务器端diff两个版本的差异, 并返回diff, native端使用patch api生成新版本的bundlejs\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cp\u003E\u003Cb\u003E方案二\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003E来自 \u003Ca href=\&https:\u002F\u002Fsegmentfault.com\u002Fu\u002Flizhiwodage\&\u003E@荔枝我大哥\u003C\u002Fa\u003E的补充\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003E我们所有的jsBundle全部加载的线上文件,通过http头信息设置`E-Tag`结合`cache-control`来实现缓存策略,最终效果就是,A.vue -& A.js, app第一次加载A.js是从网络下载下来并且保存到本地,app第二次加载A.js是直接加载的保存到本地的 A.js文件,线上A.vue被修改,A.vue -& A.js, app第三次加载A.js时根据缓存策略会知道线上A.js 已经和本地A.js 有差异,于是重新下载A.js到本地并加载. (整个流程通过http缓存策略来实现,无需多余编码。\u003C\u002Fp\u003E\u003Cp\u003E参考https:\u002F\u002Fdevelopers.google.cn\u002Fweb\u002Ffundamentals\u002Fperformance\u002Foptimizing-content-efficiency\u002Fhttp-caching?hl=zh-cn)\u003C\u002Fp\u003E\u003Cp\u003E还可以参考很多ReactNative的成熟方案, 本质上都是js的热更新。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003E降级处理\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003E一般情况下, 我们会同时部署一套web端界面, 若线上环境的weex页面出现bug, 则使用webview加载web版, 推荐依赖服务端api来控制降级的切换。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Ch2\u003E\u003Cb\u003E总结\u003C\u002Fb\u003E\u003C\u002Fh2\u003E\u003Cp\u003E\u003Cb\u003Eweex的优势: \u003C\u002Fb\u003E依托于vue, 上手简单. 可以满足以vue为技术主导的公司给native双端提供\u003Ci\u003E简单\u002F少底层交互\u002F热更新需求\u003C\u002Fi\u003E的页面的需求。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003Eweex的劣势:\u003C\u002Fb\u003E 在native端调整样式是我心中永远的痛.. 以及众所周知的生态问题, 维护组没有花太多精力解答社区问题, 官方文档错误太多, 导致我在看的时候就顺手提了几个PR。\u003C\u002Fp\u003E\u003Cp\u003E对于文章中提到的没提到的问题, 欢迎来和笔者讨论, 或者参考我的\u003Ca href=\&https:\u002F\u002Fgithub.com\u002Fw11pFweex-start-kit\&\u003Eweex-start-kit\u003C\u002Fa\u003E, 当然点个star也是极好的\u003C\u002Fp\u003E\u003Cimg src=\&v2-f4f650db1ff59e7d16a4.png\& data-rawwidth=\&182\& data-rawheight=\&180\&\u003E\u003Cp\u003E\u003C\u002Fp\u003E&,&updated&:new Date(&T03:01:45.000Z&),&canComment&:false,&commentPermission&:&review&,&commentCount&:0,&likeCount&:84,&state&:&published&,&isLiked&:false,&slug&:&&,&isTitleImageFullScreen&:false,&rating&:&none&,&sourceUrl&:&&,&publishedTime&:&T11:01:45+08:00&,&links&:{&comments&:&\u002Fapi\u002Fposts\u002F2Fcomments&},&url&:&\u002Fp\u002F&,&titleImage&:&https:\u002F\u002Fpic4.zhimg.com\u002Fv2-9bf78716cdddbda3d9db0_r.jpg&,&summary&:&&,&href&:&\u002Fapi\u002Fposts\u002F&,&meta&:{&previous&:null,&next&:null},&snapshotUrl&:&&,&commentsCount&:0,&likesCount&:84},&&:{&title&:&鹅厂原创 | 使用HTML5开发Kinect体感游戏&,&author&:&imwebqian-duan-xue-yuan&,&content&:&\u003Cblockquote\u003E本文作者:brucewan\u003C\u002Fblockquote\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Ch2\u003E\u003Cb\u003E零 写在前面\u003C\u002Fb\u003E\u003C\u002Fh2\u003E\u003Cp\u003E对前端工程师而言,开发Kinect的体感游戏似乎有些触不可及,因为目前市面上基本上是用C#或C++来开发,但如果游戏运行在我们熟悉的浏览器中,结合HTML5的新特征,我们是不是可以玩出新高度呢?\u003C\u002Fp\u003E\u003Ch2\u003E\u003Cb\u003E一 简介\u003C\u002Fb\u003E\u003C\u002Fh2\u003E\u003Cp\u003E在前不久成都TGC2016展会上,我们开发了一款《火影忍者手游》的体感游戏,主要模拟手游章节《九尾袭来 》,用户化身四代,与九尾进行对决,吸引了大量玩家参与。\u003C\u002Fp\u003E\u003Cp\u003E表面上看,这款游戏与其它体感体验无异,实际上,它一直运行于浏览器Chrome下,也就是说,我们只需要掌握前端相应技术,就可以开发基于Kinect的网页体感游戏。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Ch2\u003E\u003Cb\u003E二 如何实现\u003C\u002Fb\u003E\u003C\u002Fh2\u003E\u003Cp\u003E使用H5开发基于Kinect的体感游戏,其实工作原理很简单,由Kinect采集到玩家及环境数据,比如人体骨骼,使用某种方式,使浏览器可以访问这些数据。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003E1、采集数据\u003C\u002Fb\u003E\u003Cbr\u003EKinect有三个镜头,中间镜头类似普通摄像头,获取彩色图像。左右两边镜头则是通过红外线获取深度数据。我们使用微软提供的SDK去读取以下类型数据:\u003C\u002Fp\u003E\u003Cul\u003E\u003Cli\u003E色彩数据:彩色图像;\u003C\u002Fli\u003E\u003Cli\u003E深度数据:颜色尝试信息;\u003C\u002Fli\u003E\u003Cli\u003E人体骨骼数据:基于以上数据经计算,获取到人体骨骼数据。\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cp\u003E\u003Cb\u003E2、使浏览器可访问到Kinect数据\u003C\u002Fb\u003E\u003Cbr\u003E我尝试和了解过的框架,基本上是以socket让浏览器进程与服务器进行通信 ,进行数据传输:\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cul\u003E\u003Cli\u003EKinect-HTML5 用C#搭建服务端,色彩数据、尝试数据、骨骼数据均有提供;\u003Cbr\u003E\u003C\u002Fli\u003E\u003Cli\u003EZigFu 支持H5、U3D、Flash进行开发,API较为完整,貌似收费;\u003Cbr\u003E\u003C\u002Fli\u003E\u003Cli\u003EDepthJS
以浏览器插件形式提供数据访问;\u003Cbr\u003E\u003C\u002Fli\u003E\u003Cli\u003ENode-Kinect2 以Nodejs搭建服务器端,提供数据比较完整,实例较多。\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cp\u003E我最终选用Node-Kinect2,虽然没有文档,但是实例较多,使用前端工程师熟悉的Nodejs,另外作者反馈比较快。\u003C\u002Fp\u003E\u003Cimg src=\&v2-a602adc8efbf08ccd8eef4.jpg\& data-rawwidth=\&640\& data-rawheight=\&363\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cul\u003E\u003Cli\u003EKinect: 捕获玩家数据,比如深度图像、彩色图像等;\u003C\u002Fli\u003E\u003Cli\u003ENode-Kinect2: 从Kinect获取相应数据,并进行二次加工;\u003C\u002Fli\u003E\u003Cli\u003E浏览器: 监听node应用指定接口,获取玩家数据并完成游戏开发。\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Ch2\u003E\u003Cb\u003E三 前期准备\u003C\u002Fb\u003E\u003C\u002Fh2\u003E\u003Cp\u003E1、系统要求: 这是硬性要求,我曾在不符合要求的环境下浪费太多时间。\u003C\u002Fp\u003E\u003Cul\u003E\u003Cli\u003EUSB3.0\u003C\u002Fli\u003E\u003Cli\u003E支持DX11的显卡\u003C\u002Fli\u003E\u003Cli\u003Ewin8及以上系统\u003C\u002Fli\u003E\u003Cli\u003E支持Web Sockets的浏览器\u003C\u002Fli\u003E\u003Cli\u003E当然Kinect v2传感器是少不了的\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cp\u003E2、环境搭建流程:\u003C\u002Fp\u003E\u003Col\u003E\u003Cli\u003E连接上Kinect v2\u003C\u002Fli\u003E\u003Cli\u003E安装 KinectSDK-v2.0\u003C\u002Fli\u003E\u003Cli\u003E安装 Nodejs\u003C\u002Fli\u003E\u003Cli\u003E安装 Node-Kinect2: npm install kinect2\u003C\u002Fli\u003E\u003C\u002Fol\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Ch2\u003E\u003Cb\u003E四 实例演示\u003C\u002Fb\u003E\u003C\u002Fh2\u003E\u003Cp\u003E如下图所示,我们演示如何获取人体骨骼,并标识脊椎中段及手势:\u003C\u002Fp\u003E\u003Cimg src=\&v2-cef2c7f7c6fb401dba9af.jpg\& data-rawwidth=\&640\& data-rawheight=\&488\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E1、服务器端 创建web服务器,并将骨骼数据发送到浏览器端,代码如下:\u003C\u002Fp\u003E\u003Ccode lang=\&js\&\u003Evar Kinect2 = require('..\u002F..\u002Flib\u002Fkinect2'),\n\texpress = require('express'),\n\tapp = express(),\n\tserver = require('http').createServer(app),\n\tio = require('socket.io').listen(server);\n\nvar kinect = new Kinect2();\n\u002F\u002F 打开kinect\nif(kinect.open()) {\n\t\u002F\u002F 监听8000端口\n\tserver.listen(8000);\n\t\u002F\u002F 指定请求指向根目录\n\tapp.get('\u002F', function(req, res) {\n\t\tres.sendFile(__dirname + '\u002Fpublic\u002Findex.html');\n\t});\n\t\u002F\u002F 将骨骼数据发送给浏览器端\n\tkinect.on('bodyFrame', function(bodyFrame){\n\t\tio.sockets.emit('bodyFrame', bodyFrame);\n\t});\n\t\u002F\u002F 开始读取骨骼数据\n\tkinect.openBodyReader();\n}\u003C\u002Fcode\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E2、浏览器端 浏览器端获取骨骼数据,并用canvas描绘出来,关键代码如下:\u003C\u002Fp\u003E\u003Ccode lang=\&js\&\u003Evar socket = io.connect('\u002F');\nvar ctx = canvas.getContext('2d');\nsocket.on('bodyFrame', function(bodyFrame){\n\tctx.clearRect(0, 0, canvas.width, canvas.height);\n\tvar index = 0;\n\t\u002F\u002F 遍历所有骨骼数据\n\tbodyFrame.bodies.forEach(function(body){\n\t\tif(body.tracked) {\n\t\t\tfor(var jointType in body.joints) {\n\t\t\t\tvar joint = body.joints[jointType];\n\t\t\t\tctx.fillStyle = colors[index];\n\t\t\t\t\u002F\u002F 如果骨骼节点为脊椎中点\n\t\t\t\tif(jointType == 1) {\n\t\t\t\t\tctx.fillStyle = colors[2];\n\t\t\t\t}\n\t\t\t\tctx.fillRect(joint.depthX * 512, joint.depthY * 424, 10, 10);\n\t\t\t}\n\t\t\t\u002F\u002F 识别左右手手势\n\t\t\tupdateHandState(body.leftHandState, body.joints[7]);\n\t\t\tupdateHandState(body.rightHandState, body.joints[11]);\n\t\t\tindex++;\n\t\t}\n\t});\n});\u003C\u002Fcode\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E很简单的几行代码,我们便完成了玩家骨骼捕获,有一定 javascript基础的同学应该很容易能看明白,但不明白的是我们能获取哪些数据?如何获取?骨骼节点名称分别是什么?而node-kienct2并没有文档告诉我们这些。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Ch2\u003E\u003Cb\u003E五 开发文档\u003C\u002Fb\u003E\u003C\u002Fh2\u003E\u003Cp\u003ENode-Kinect2并没有提供文档,我将我测试总结的文档整理如下:\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003E1、服务器端能提供的数据类型;\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Ccode lang=\&js\&\u003Ekinect.on('bodyFrame', function(bodyFrame){}); \u002F\u002F还有哪些数据类型呢?\u003C\u002Fcode\u003E\u003Cimg src=\&v2-9de86affeaae9.png\& data-rawwidth=\&652\& data-rawheight=\&360\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003E2、骨骼节点类型\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Ccode lang=\&js\&\u003Ebody.joints[11] \u002F\u002F joints包括哪些呢?\u003C\u002Fcode\u003E\u003Cimg src=\&v2-0ad9cf98db435ccfb0fa.png\& data-rawwidth=\&596\& data-rawheight=\&938\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003E3、手势,据测识别并不是太准确,在精度要求不高的情况下使用\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-af83ee2ebefcd55b244c40.png\& data-rawwidth=\&579\& data-rawheight=\&181\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003E4、骨骼数据 \u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003Ebody [object] {\u003Cbr\u003E
bodyIndex [number]:索引,允许6人\u003Cbr\u003E
joints [array]:骨骼节点,包含坐标信息,颜色信息\u003Cbr\u003E
leftHandState [number]:左手手势\u003Cbr\u003E
rightHandState [number]:右手手势\u003Cbr\u003E
tracked [boolean]:是否捕获到\u003Cbr\u003E
trackingId\u003Cbr\u003E} \u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003E5、kinect对象\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-bee386e809b.png\& data-rawwidth=\&525\& data-rawheight=\&179\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Ch2\u003E\u003Cb\u003E六 实战总结\u003C\u002Fb\u003E\u003C\u002Fh2\u003E\u003Cp\u003E接下来,我总结一下TGC2016《火影忍者手游》的体感游戏开发中碰到的一些问题。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003E1、讲解之前,我们首先需要了解下游戏流程。\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-765b63d3aba8d2a483ae2.png\& data-rawwidth=\&593\& data-rawheight=\&507\&\u003E\u003Cp\u003E\u003Cb\u003E2、服务器端 \u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003E游戏需要玩家骨骼数据(移动、手势),彩色图像数据(某一手势下触发拍照),所以我们需要向客户端发送这两部分数据。值得注意的是,彩色图像数据体积过大,需要进行压缩。\u003C\u002Fp\u003E\u003Ccode lang=\&js\&\u003Evar emitColorFrame =\nio.sockets.on('connection', function (socket){\n\tsocket.on('startColorFrame', function(data){\n\t\temitColorFrame =\n\t});\t\n});\nkinect.on('multiSourceFrame', function(frame){\n\n\t\u002F\u002F 发送玩家骨骼数据\n\tio.sockets.emit('bodyFrame', frame.body);\n\n\t\u002F\u002F 玩家拍照\n\tif(emitColorFrame) {\n\t\tvar compression = 1;\n\t\tvar origWidth = 1920;\n\t\tvar origHeight = 1080;\n\t\tvar origLength = 4 * origWidth * origH\n\t\tvar compressedWidth = origWidth \u002F\n\t\tvar compressedHeight = origHeight \u002F\n\t\tvar resizedLength = 4 * compressedWidth * compressedH\n\t\tvar resizedBuffer = new Buffer(resizedLength);\n\t\t\u002F\u002F ...\n\t\t\u002F\u002F 照片数据过大,需要压缩提高传输性能\n\t\tzlib.deflate(resizedBuffer, function(err, result){\n\t\t\tif(!err) {\n\t\t\t\tvar buffer = result.toString('base64');\n\t\t\t\tio.sockets.emit('colorFrame', buffer);\n\t\t\t}\n\t\t});\t\t\t\n\t\temitColorFrame =\n\t}\n});\nkinect.openMultiSourceReader({\n\tframeTypes: Kinect2.FrameType.body | Kinect2.FrameType.color\n});\u003C\u002Fcode\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003E3、客户端 \u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003E客户端业务逻辑较复杂,我们提取关键步骤进行讲解。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003E3.1\u003C\u002Fb\u003E、用户拍照时,由于处理的数据比较大,为防止页面出现卡顿,我们需要使用web worker\u003C\u002Fp\u003E\u003Ccode lang=\&js\&\u003E(function(){\n
importScripts('pako.inflate.min.js'); \n\n
var imageD\n
function init() {\n
addEventListener('message', function (event) {\n
switch (event.data.message) {\n
case \&setImageData\&:\n
imageData = event.data.imageD\n
case \&processImageData\&:\n
processImageData(event.data.imageBuffer);\n
function processImageData(compressedData) {\n
var imageBuffer = pako.inflate(atob(compressedData));\n
var pixelArray = imageData.\n
var newPixelData = new Uint8Array(imageBuffer);\n
var imageDataSize = imageData.data.\n
for (var i = 0; i & imageDataS i++) {\n
imageData.data[i] = newPixelData[i];\n
for(var x = 0; x & 1920; x++) {\n
for(var y = 0; y & 1080; y++) {\n
var idx = (x + y * 1920) * 4;\n
var r = imageData.data[idx + 0]; \n
var g = imageData.data[idx + 1]; \n
var b = imageData.data[idx + 2]; \n
self.postMessage({ \&message\&: \&imageReady\&, \&imageData\&: imageData });\n
init();\n})();
\u003C\u002Fcode\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003E3.2\u003C\u002Fb\u003E、接投影仪后,如果渲染面积比较大,会出现白屏,需要关闭浏览器硬件加速。\u003C\u002Fp\u003E\u003Cimg src=\&v2-ef1cdee5acf6d3a9264ca3.png\& data-rawwidth=\&659\& data-rawheight=\&95\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003E3.3\u003C\u002Fb\u003E、现场光线较暗,其它玩家干扰,在追踪玩家运动轨迹的过程中,可能会出现抖动的情况,我们需要去除干扰数据。(当突然出现很大位移时,需要将数据移除)\u003C\u002Fp\u003E\u003Ccode lang=\&js\&\u003Evar tracks = this.\nvar len = tracks.\n\n\u002F\u002F 数据过滤\nif(tracks[len-1] !== window.undefined) {\n\tif(Math.abs(n - tracks[len-1]) & 0.2) {\n\t\\n\t}\n}\nthis.tracks.push(n);\u003C\u002Fcode\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003E3.4\u003C\u002Fb\u003E、当玩家站立,只是左右少量晃动时,我们认为玩家是站立状态。\u003C\u002Fp\u003E\u003Ccode lang=\&js\&\u003E\u002F\u002F 保留5个数据\nif(this.tracks.length & 5) {\n\tthis.tracks.shift();\n} else {\n\\n}\n\n\u002F\u002F 位移总量\nvar dis = 0;\nfor(var i = 1; i & this.tracks. i++) {\n\tdis += this.tracks[i] - this.tracks[i-1];\n}\nif(Math.abs(dis) & 0.01) {\n\tthis.stand();\n} else {\n\tif(this.tracks[4] & this.tracks[3]) {\n\t\tthis.turnRight();\n\t} else {\n\t\tthis.turnLeft();\n\t}\n\tthis.run();\n}\u003C\u002Fcode\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Ch2\u003E\u003Cb\u003E七 实战总结\u003C\u002Fb\u003E\u003C\u002Fh2\u003E\u003Cp\u003E1、使用HTML5开发Kinect体感游戏,降低了技术门槛,前端工程师可以轻松的开发体感游戏; \u003C\u002Fp\u003E\u003Cp\u003E2、大量的框架可以应用,比如用JQuery、CreateJS、Three.js(三种不同渲染方式); \u003C\u002Fp\u003E\u003Cp\u003E3、无限想象空间,试想下体感游戏结合webAR,结合webAudio、结合移动设备,太可以挖掘的东西了……想想都激动不是么!\u003C\u002Fp\u003E\u003Cp\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003C\u002Fp\u003E&,&updated&:new Date(&T03:30:56.000Z&),&canComment&:false,&commentPermission&:&anyone&,&commentCount&:5,&likeCount&:24,&state&:&published&,&isLiked&:false,&slug&:&&,&isTitleImageFullScreen&:false,&rating&:&none&,&sourceUrl&:&&,&publishedTime&:&T11:30:56+08:00&,&links&:{&comments&:&\u002Fapi\u002Fposts\u002F2Fcomments&},&url&:&\u002Fp\u002F&,&titleImage&:&https:\u002F\u002Fpic4.zhimg.com\u002Fv2-8f620fdac5c73e4bed6689_r.jpg&,&summary&:&&,&href&:&\u002Fapi\u002Fposts\u002F&,&meta&:{&previous&:null,&next&:null},&snapshotUrl&:&&,&commentsCount&:5,&likesCount&:24},&&:{&title&:&名人堂 | W3CPlus中国创始人大漠:前端路上的旅行&,&author&:&imwebqian-duan-xue-yuan&,&content&:&\u003Cp\u003E\u003C\u002Fp\u003E\u003Ch2\u003E\u003Cb\u003E关于作者\u003C\u002Fb\u003E\u003C\u002Fh2\u003E\u003Cp\u003E常用昵称“大漠”,W3CPlus,Sass中国创始人,目前就职于手淘。中国Drupal社区核心成员之一。对HTML5、CSS3和Sass等前端脚本语言有非常深入的认识和丰富的实践经验,尤其专注对CSS3的研究,是国内最早研究和使用CSS3技术的一批人。CSS3、Sass和Drupal中国布道者。2014年出版《图解CSS3:核心技术与案例实战》。 \u003C\u002Fp\u003E\u003Ch2\u003E\u003Cb\u003E0 写在前面\u003C\u002Fb\u003E\u003C\u002Fh2\u003E\u003Cblockquote\u003E\u003Cb\u003E一入前端深似海\u003C\u002Fb\u003E\u003C\u002Fblockquote\u003E\u003Cp\u003E从步入社会算起到今天,风风雨雨走过了15个年头,望着天空,掐指一算,从事前端已近五个年头。也算三分之一的时间在做前端,也是从事过时间最久的一份工作。问我为什么能做这么久?其实我也时常问自己,怎么就进前端这个行当,怎么就整了五年。正如网上所言,一入前端深似海。\u003C\u002Fp\u003E\u003Cp\u003E玩了五年,或许后面还会有一个五年,两个五年,或许...... 未来的路,谁与说不清楚,我只想每天走好自己想走的路。\u003C\u002Fp\u003E\u003Cp\u003E最近在万能的知乎上常有同学邀请我回答:\u003C\u002Fp\u003E\u003Cul\u003E\u003Cli\u003E”今年26岁,女生,想要学WEB前端开发,0基础?“\u003C\u002Fli\u003E\u003Cli\u003E”作为一个刚入门的前端爱好者,以后立志成为前端攻城狮的我,应该要学习哪些方面的知识?“\u003C\u002Fli\u003E\u003Cli\u003E”我想做web前端,怎么学习 ?“\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cp\u003E其实我也回答不好。无法很好回答的情况之下,促使自己在思考,思考这些年来在前端路上的旅行。也让我写下了这篇文章。\u003C\u002Fp\u003E\u003Cimg src=\&v2-5f4d5e110beb375b75b5e7.png\& data-rawwidth=\&248\& data-rawheight=\&224\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Ch2\u003E\u003Cb\u003E1 聊聊前端\u003C\u002Fb\u003E\u003C\u002Fh2\u003E\u003Cblockquote\u003E什么是前端?\u003C\u002Fblockquote\u003E\u003Cp\u003E大部分指的是Web前端开发,这个词是从网页制作演变过来的,名称上有着很明显的时代特征。在互联网的演化过程中,网页制作是Web1.0时代的产物,那时网站的主要内容都是静态的,用户使用网站的行为也以浏览为主。在这个阶段,网站的内容主要是文字内容和图片为主,制作方法也主要是使用表格拼装。印象中,那时主要是FrontPage这样的,画个表格,往里面填真图片,文字,就叫网页了。\u003C\u002Fp\u003E\u003Cimg src=\&v2-333d5e1e2e969bab6d6c.png\& data-rawwidth=\&382\& data-rawheight=\&264\&\u003E\u003Cp\u003E2005年以后,互联网进入了Web2.0时代,各类似桌面软件的Web应用大量涌现,网站的前端由此发生了翻天覆地的变化。网页也不再只是承载单一的文字和图片,各种富媒体让网页的内容更加生动,网页上软件化的交互形式为用户提供了更多好的使用体验,这些都基于前端技术实现。\u003C\u002Fp\u003E\u003Cimg src=\&v2-6a9fefb45d18b.png\& data-rawwidth=\&416\& data-rawheight=\&277\&\u003E\u003Cp\u003E在这个时代,给前端冠上的名称也多了,”做网站的“、”美工“、”网站设计师“、”切页面的“、”前端工程师“等等。同时技术也更为复杂化了,难度也更大了,同时技术含量也高了,那么随着这些变化,从事前端的人员也有一个专业的名号了”\u003Cb\u003E前端工程师\u003C\u002Fb\u003E“,收入也随着水涨船高了。\u003C\u002Fp\u003E\u003Cimg src=\&v2-a3ae2e1df6abfb56eaaba.png\& data-rawwidth=\&316\& data-rawheight=\&171\&\u003E\u003Cp\u003E技术发展是日新月异,现在业内人员开始提出Web3.0的概念。\u003C\u002Fp\u003E\u003Cimg src=\&v2-a702c8c365fe69d4c5af14660fb2eeb7.png\& data-rawwidth=\&477\& data-rawheight=\&258\&\u003E\u003Cp\u003E最常见的解释是,网站内的信息可以直接和其他网站相关信息进行交互,能通过第三方信息平台同时对多家网站的信息进行整合使用;用户在互联网上拥有自己的数据,并能在不同网站上使用;完全基于Web,用浏览器即可实现复杂系统程序才能实现的系统功能;用户数据审计之后,同步于网络数据。\u003C\u002Fp\u003E\u003Cp\u003E那么问题就来了?\u003C\u002Fp\u003E\u003Cimg src=\&v2-83233d93bebbb377ff72e0af310919ad.png\& data-rawwidth=\&353\& data-rawheight=\&336\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E在Web3.0的时代,前端又是什么呢?所谓的\&前端工程师\&又能做些什么呢?又能给Web带来什么呢?这些都值得我们一起思考。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Ch2\u003E\u003Cb\u003E2 我是怎样变成前端工程师?\u003C\u002Fb\u003E\u003C\u002Fh2\u003E\u003Cp\u003E我是怎样变成一枚前端工程师?其实我一直都是一枚”\u003Cb\u003E伪前端工程师\u003C\u002Fb\u003E“,充其量就是一枚会做Web页面的从业人员,不敢以工程师自居(有辱工程师这一高尚的称谓)。其实我更喜欢把自己称为”页面仔“,或者Web的美容师。或许你又会说:”大神“你谦虚了。其实不是的,当你坚持阅读完后面的内容,你也会有这样的想法。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003E2.1 什么是前端工程师?\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003E前端工程师,也被称作Web前端开发工程师(在一些公司又将其分为:前端工程师的初级、中级、高级、专家和资深等)。他是随着Web发展,细分出来的行业。\u003C\u002Fp\u003E\u003Cp\u003E简单点定义前端工程师:\u003Cb\u003E运用前端技术,实现体验的良好传达\u003C\u002Fb\u003E。简单点理解,就是使用Web的前端相关技术,实现一个用户体验良好的网站。而Web前端开发技术主要包括三个要素:结构层HTML,表现层CSS,交互层JavaScript(也有人称之为行为层)。\u003C\u002Fp\u003E\u003Cp\u003E对于前端工发工程师而言不仅要掌握基本的Web前端技术,网站性能优化,SEO和服务端的基础知识,还要学会运用各种工具进行辅助开发以及理论层面的知识,包括代码的可维护性、组件的易用性、分层主义模板和浏览器分级支持等。\u003C\u002Fp\u003E\u003Cp\u003E就此而言,我能对上号,就是对HTML和CSS有一定的了解。也就称不上是一枚前端工程师。这也就是为什么一直强调自己不是Web工程师,而只是一枚Web的美容师。不过对于我是不是什么,并不太重要,既然扯了这么多,咱们开始来聊点实际的,有用的东西。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003E2.2 前端工程师要掌握的技术\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003EWeb前端技术包括三个要素:HTML、CSS和JavaScript,但随着RIA的流行和普及,Flash\u002FFlex、Silverlight、XML和服务器端语言也是前端开发工程师应该掌握的。Web前端开发工程师既要与上游的交互设计师、视觉设计师和产品经理沟通,又要与下游的服务器端工程师沟通,需要掌握的技能非常多。这就从\u003Cb\u003E知识的广度上\u003C\u002Fb\u003E对Web前端开发工程师提出了要求。\u003C\u002Fp\u003E\u003Cp\u003E从网上扒了张图,都说一图胜千言万语:\u003C\u002Fp\u003E\u003Cimg src=\&v2-e4a2783f9f.png\& data-rawwidth=\&580\& data-rawheight=\&351\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E作为前端开发者,首先要掌握的就是HTML+CSS,有了这两项最基础的知识,就能得心应手的将设计师的Web页面切出来(将PSD设计图转换成Web静态页面)。但这还是不够的,还需要能\u003Cb\u003E熟练的处理各浏览器的兼容\u003C\u002Fb\u003E。为了能更好的要求自己,尽量要让自己的Web页面符合\u003Cb\u003EW3C标准和语义化的规范\u003C\u002Fb\u003E。这方面虽然不是硬性的要求,但是做好这两者很有必要,也具有较大的难度。\u003C\u002Fp\u003E\u003Cimg src=\&v2-6fc129ace5b.png\& data-rawwidth=\&529\& data-rawheight=\&303\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E充分掌握了HTML和CSS方面知识后,接下去要把\u003Cb\u003EJavaScript\u003C\u002Fb\u003E拿下,这也是前端工程师必备技能之一,也算是较难的一部分(我至今写不了JS,所以...你懂的)。只懂HTML、CSS和JavaScript其中一个或两个还不行,你必须对这三门语言都很熟悉。也不是说必须对这三门语言都非常精通,但你至少要能够运用它们完成大多数任务,而无需频繁地寻求别人的帮助。\u003C\u002Fp\u003E\u003Cimg src=\&v2-cf8c2521.png\& data-rawwidth=\&336\& data-rawheight=\&318\&\u003E\u003Cp\u003E优秀的前端工程师应该具备\u003Cb\u003E快速学习能力\u003C\u002Fb\u003E。推动Web发展的技术并不是静止不动的,没错吧?我甚至可以说这些技术几乎每天都在变化,如果没有快速学习能力,你就跟不上Web发展的步伐。你必须不断提升自己,不断学习新技术、新模式;仅仅依靠今天的知识无法适应未来。\u003C\u002Fp\u003E\u003Cp\u003EWeb的明天与今天必将有天壤之别,而你的工作就是要搞清楚如何通过自己的Web应用程序来体现这种翻天覆地的变化。\u003C\u002Fp\u003E\u003Cp\u003E优秀的前端工程师需要具备良好的沟通能力,因为你的工作与很多人的工作息息相关。在任何情况下,前端工程师至少都要满足下列四类客户的需求。\u003C\u002Fp\u003E\u003Cul\u003E\u003Cli\u003E\u003Cb\u003E产品经理\u003C\u002Fb\u003E——这些是负责策划应用程序的一群人。他们能够想象出怎样通过应用程序来满足用户需求,以及怎样通过他们设计的模式赚到钱(但愿如此)。一般来说,这些人追求的是丰富的功能。\u003C\u002Fli\u003E\u003Cli\u003E\u003Cb\u003EUI设计师\u003C\u002Fb\u003E——这些人负责应用程序的视觉设计和交互模拟。他们关心的是用户对什么敏感、交互的一贯性以及整体的好用性。他们热衷于流畅靓丽但并不容易实现的用户界面。\u003C\u002Fli\u003E\u003Cli\u003E\u003Cb\u003E项目经理\u003C\u002Fb\u003E——这些人负责实际地运行和维护应用程序。项目管理的主要关注点,无外乎正常运行时间(uptime)——应用程序始终正常可用的时间、性能和截止日期。项目经理追求的目标往往是尽量保持事情的简单化,以及不在升级更新时引入新问题。\u003C\u002Fli\u003E\u003Cli\u003E\u003Cb\u003E最终用户\u003C\u002Fb\u003E——当然是应用程序的主要消费者。尽管我们不会经常与最终用户打交道,但他们的反馈意见至关重要;没人想用的应用程序毫无价值。最终用户要求最多的就是对个人有用的功能,以及竞争性产品所具备的功能。\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cp\u003E如果你想把自己变得更为强大,处于不败之地,那你需要根据下图去提高自已:\u003C\u002Fp\u003E\u003Cimg src=\&v2-d42b68456ecb81cddbe4e317e7003e49.png\& data-rawwidth=\&568\& data-rawheight=\&531\&\u003E\u003Cp\u003E看到这里,你是不是对“前端工程师”有更深入的了解呢?当然,你看到这里也,或许方向更明确,或许更惧怕。因为有很多前端人员就是因为这些原因离开前端的战场,转站其他的IT岗位。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003E2.3
回到当初的问题\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003E既然你知道了什么是前端工程师,前端工程师要掌握些什么?接下来回到前面自设的问题——\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003E我是怎么变为一名\&前端工程师\&?\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003E从我从业的年龄和从事前端工作时间推算,我是典型的一名半路出家的和尚。五年前,因为种种原因,放弃自己的专业投入了互联网的阵营,学了HTML+CSS,接下来顺理成章的就成了一名”前端“,也可谓是一入前端深似海。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003E借此想告诉一些想转行做前端的同学\u003C\u002Fb\u003E?在转行从事前端这份工作,需要经历一个很长的时间跨度,你需要花费更多的精力、做更多的项目,进行更多的反思和总结才能理解某个知识点的本质。当然,如果你有这方面的天赋,或许你不要经历这些。\u003C\u002Fp\u003E\u003Cp\u003E同时你还要\u003Cb\u003E摆脱很多思维定势和禁锢\u003C\u002Fb\u003E,当然,如果你有一位师傅带你入道,那你是幸运儿。但不管怎么样,我始终认为应当秉承兴趣第一的原则,不管你是误打误撞,还是意欲为之,兴趣能促使你有更多的动力去学习,去做。你也能做得更好。\u003C\u002Fp\u003E\u003Cblockquote\u003E“我对Web开发人员最大的建议就是:热爱你的工作。热爱跨浏览器开发带来的挑战、热爱互联网技术的种种异端,热爱业内的同行,热爱你的工 具。互联网发展太快了,如果你不热爱它的话,不可能跟上它的步伐。这意味着你必须多阅读,多动手,保证自己的才能与日俱增。下了班也不能闲着,要做一些对自己有用的 事儿。可以参与一些开源软件的开发,读读好书,看看牛人的博客。经常参加一些会议,看看别人都在干什么。要想让自己快速成长,有很多事儿可以去做,而且付出一定会有回报。“ ————@N.C.Zakas\u003C\u002Fblockquote\u003E\u003Cp\u003E其实做前端的很多同学都和我一样是半路出家。拿我来说,当初选择做前端主要:\u003C\u002Fp\u003E\u003Cul\u003E\u003Cli\u003E讨厌现在的工作,想换一份能更轻松的工作,也能帮我改善生活的工作;\u003C\u002Fli\u003E\u003Cli\u003E前端入门门槛简单,学点HTML+CSS就能开始找份工作,边做边学;\u003C\u002Fli\u003E\u003Cli\u003E前端是一门所见即所得,你做了就能看到效果,能清楚的知道自己做得对还是错。\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cp\u003E随着时间的久已,做别的也做不了,也不想做。也就坚持一直在做,不过今天的我,把前端不再当做一份工作,而是将其\u003Cb\u003E当做自己的兴趣爱好\u003C\u002Fb\u003E。所以我自我感觉越来越良好,越来越轻松,虽然还有很多技术,很多知识要学习,但我一直坚持在学习,让自己能紧跟时代的步伐,尽量不让自己过早的淘汰。\u003C\u002Fp\u003E\u003Cp\u003E半路出家的人员心态都比较走两个极端,一是看中他的前景;二者是讨厌原工作。我自己就是属于二者。但不管是你已转行了,还是将要转行做前端,我都建议您静下心来思考下面几个问题:\u003C\u002Fp\u003E\u003Cul\u003E\u003Cli\u003E我能做什么?\u003C\u002Fli\u003E\u003Cli\u003E我不能做什么?\u003C\u002Fli\u003E\u003Cli\u003E我的优势是什么?\u003C\u002Fli\u003E\u003Cli\u003E我的劣势是什么?\u003C\u002Fli\u003E\u003Cli\u003E做前端对我有何好处?\u003C\u002Fli\u003E\u003Cli\u003E做前端我要付出何种代价?\u003C\u002Fli\u003E\u003Cli\u003E我能不能坚持做下去?\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cp\u003E如果你不能好好的,清楚的回答这些问题,我建议你还是不要轻意转行做前端。\u003C\u002Fp\u003E\u003Cimg src=\&v2-0ba23cfcc59ed1f430011c.png\& data-rawwidth=\&370\& data-rawheight=\&349\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Ch2\u003E\u003Cb\u003E3 如何学习前端?\u003C\u002Fb\u003E\u003C\u002Fh2\u003E\u003Cp\u003E学习对于每一个人都有自己独特的方式,我也不另外。在此我不能说我的学习方法就是好的,我只是想借此机会说说自己是如何学习前端技术的。\u003C\u002Fp\u003E\u003Cp\u003E做为一位半路出家的和尚而言,在还没开始学习前端之前,我内心是很惧怕的,我怕我隔了十年之后,还能不能静下心来读书。还能不能读懂。当初我也是抱着试试看的心态,因为我不在年轻,因为我有家庭,因为有各种压力。\u003C\u002Fp\u003E\u003Cp\u003E既然选择了,我就放下了。抱着《HTML+CSS从入门到精通》和《CSS Cookbook》:\u003C\u002Fp\u003E\u003Cimg src=\&v2-e0f18e02532d1aae8dd264fe70e94ee7.png\& data-rawwidth=\&305\& data-rawheight=\&427\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-bcbed83ea.png\& data-rawwidth=\&304\& data-rawheight=\&428\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E开始了前端的学习。在整个学习的过程是一件痛苦的过程,在只看书的情况之下,并不清楚自己是否看懂了,是否掌握了。基于这种情况之下,我在只知道HTML标签和如何使用CSS属性情形之下,我开始了\u003Cb\u003E不断的仿站\u003C\u002Fb\u003E。\u003C\u002Fp\u003E\u003Cp\u003E与其说是仿站还不如说是抄。印象中最早就是拿着Wordpress官网,借着Firebug工具(这是一个神奇的工具,让我少了很多烦恼)练习。就是先抄其HTML,然后抄其CSS。抄一段,在浏览器刷新一次看效果,就是这样的一个过程,让我找到了学习的自信,我觉得我自己能学会。就这样一段时间后,我自己能独立做出一个外表长得和原网站一样的站(没有任何的数据、没有任何的交互,没做任何的兼容)。\u003C\u002Fp\u003E\u003Cp\u003E这样是不够的,接下来,为了自己能更多的了解HTML+CSS,我一直在走着同样的路,在Website Templates寻找喜欢的,觉得简单的模板,开始仿做。但是对于前端,需要将PSD设计图转换成Web页面,那前面的过程是不够的,需要尝试将PSD切出Web页面。互联网是成能的,我在网站上下载了很多PSD设计图,然后将其转换成Web页面。\u003C\u002Fp\u003E\u003Cimg src=\&v2-bdb0191ec60d.png\& data-rawwidth=\&575\& data-rawheight=\&312\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003E国外优秀的UI设计资源库\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003E经过一段时间之后,拿着仿出来的凡客诚品去找工作,值得幸运的是,有了一份前端的工作。在这个工作当中,让我带来更多的机会,让我能更好的去理解和学习的场景。\u003C\u002Fp\u003E\u003Cp\u003E有了一年工作经验之后,我需要的是开始沉淀自己。我在开始思考:\u003C\u002Fp\u003E\u003Cul\u003E\u003Cli\u003E我掌握了什么?\u003C\u002Fli\u003E\u003Cli\u003E哪些似懂非懂?\u003C\u002Fli\u003E\u003Cli\u003E哪些不懂?\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cp\u003E有了这三份清单,我又开始有了新的目标,针对清单上的去做总结,去把似懂不懂和不懂的搞懂。于是,我有了自己的第一个博客——\u003Cb\u003EW3cplus\u003C\u002Fb\u003E。在这个博客上记下了我学习的历程和积累的前端知识。更让我值得庆幸的是,这个网站让我认识了更多的同行朋友,也给我带来了更多的机会。\u003C\u002Fp\u003E\u003Cp\u003E其实这些记录的都是一些零散的知识点,如果要系统的拿下一个知识,那还是不够的。在这种情形之下,2012年开始了两年漫长的写书过程,在这两年时间当中,我一直在学习和整理自己掌握的CSS3知识,这就是《《图解CSS3:核心技术与案例实战》》\u003C\u002Fp\u003E\u003Cimg src=\&v2-822fc9e9ac965daa4dac.png\& data-rawwidth=\&250\& data-rawheight=\&272\&\u003E\u003Cp\u003E在这个过程,我学会了去读W3C规范,也让我更清楚的了解了CSS。更值得庆幸的是,我学会了如何搜索自己需要寻找的知识,我想这才是最值钱的一部分吧。\u003C\u002Fp\u003E\u003Cp\u003E看到这里,或许你会认为我好傻,其实我真的好傻。但我一直认为,既然自己没有天赋,就需要付出比常人更多的时间。\u003C\u002Fp\u003E\u003Cp\u003E简单点归纳:\u003C\u002Fp\u003E\u003Cul\u003E\u003Cli\u003E\u003Cb\u003E多看:\u003C\u002Fb\u003E多看书,多看规范,多看优秀的网站,多看优秀的代码\u003C\u002Fli\u003E\u003Cli\u003E\u003Cb\u003E多问:\u003C\u002Fb\u003E多问几个为什么?多问几个出处?多问几个原由\u003C\u002Fli\u003E\u003Cli\u003E\u003Cb\u003E多做:\u003C\u002Fb\u003E多做练习,实战出真理;多做,才能有更多经验\u003C\u002Fli\u003E\u003Cli\u003E\u003Cb\u003E多想:\u003C\u002Fb\u003E多思考几个为什么?\u003C\u002Fli\u003E\u003Cli\u003E\u003Cb\u003E多总结:\u003C\u002Fb\u003E多总结看到、想到的、听到的,这些沉淀下来的都是你自己的\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Ch2\u003E\u003Cb\u003E4 我的前端瓶颈\u003C\u002Fb\u003E\u003C\u002Fh2\u003E\u003Cp\u003E记得曾经有位前辈曾说,如果你一年、两年都在折腾同一样的东西,那意味着你没有任何的进步。其实我现在就面临这样的现象,几年来一直在折腾CSS这样的独技,而对于其他的并未有掌握,或者说了解。\u003C\u002Fp\u003E\u003Cimg src=\&v2-b4f13d1cf.png\& data-rawwidth=\&283\& data-rawheight=\&254\&\u003E\u003Cp\u003E这让自己显得迷茫,与想得,离得甚远。特别是当今的前端,如果仅仅掌握HTML和CSS是越来越不够了,在今年,在这个新的团队当中,我越来越感觉到,掌握的东西太少,需要的学习的东西太多。因为想做的事情也太多。这也让我有了更大的压力,人家都说压力就是动力,有了动力就有冲劲。\u003C\u002Fp\u003E\u003Cp\u003E不过,由于自己掌握的少,什么都想学,这也让自已感到迷茫,似乎没了方向。这也是我最近在思考的事情,我应该如何往下走,我应该如何不被前端的大潮迷失了自己,失去了方向。可是我到今天还没有想清楚,但我希望有一天我能想清楚,我也更希望这样的一天能早点到来。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Ch2\u003E\u003Cb\u003E5 总结\u003C\u002Fb\u003E\u003C\u002Fh2\u003E\u003Cp\u003E无论从哪个方面讲,我都觉得前端工程师是计算机科学职业领域中最复杂的一个工种。绝大多数传统的编程思想已经不适用了,为了在多种平台中使用,多种技术都借鉴了大量软科学的知识和理念。\u003C\u002Fp\u003E\u003Cp\u003E成为优秀前端工程师所要具备的专业技术,涉及到广阔而复杂的领域,这些领域又会因为你最终必须服务的各方的介入而变得更加复杂。专业技术可能会引领你进入成为前端工程师的大门,但只有运用该技术创造的应用程序以及你跟他人并肩协同的能力,才会真正让你变得优秀。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E原文:http:\u002F\u002Fwww.w3cplus.com\u002Ffront-end-trip-on-road.html\u003C\u002Fp\u003E\u003Cp\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003C\u002Fp\u003E&,&updated&:new Date(&T03:08:19.000Z&),&canComment&:false,&commentPermission&:&anyone&,&commentCount&:7,&likeCount&:51,&state&:&published&,&isLiked&:false,&slug&:&&,&isTitleImageFullScreen&:false,&rating&:&none&,&sourceUrl&:&&,&publishedTime&:&T11:08:19+08:00&,&links&:{&comments&:&\u002Fapi\u002Fposts\u002F2Fcomments&},&url&:&\u002Fp\u002F&,&titleImage&:&https:\u002F\u002Fpic4.zhimg.com\u002Fv2-b4efbd3ada96ae_r.jpg&,&summary&:&&,&href&:&\u002Fapi\u002Fposts\u002F&,&meta&:{&previous&:null,&next&:null},&snapshotUrl&:&&,&commentsCount&:7,&likesCount&:51},&&:{&title&:&鹅厂原创 | 前端中的函数式编程&,&author&:&imwebqian-duan-xue-yuan&,&content&:&\u003Cp\u003E\u003C\u002Fp\u003E\u003Cp\u003E
文\u002Fbaixinchen\u003C\u002Fp\u003E\u003Cp\u003E
腾讯SNG事业群——web前端开发工程师\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Ch2\u003E\u003Cb\u003E0 写在前面\u003C\u002Fb\u003E\u003C\u002Fh2\u003E\u003Cblockquote\u003E\u003Cb\u003E 前端的技术革新从来没有停止过,但从最近的趋势来看,貌似有一个“新”名词出现,那就是函数式编程(FP,functional programming)。 \u003C\u002Fb\u003E\u003C\u002Fblockquote\u003E\u003Cp\u003Evue、react这些热门的框架都多多少少有点涉及到函数式编程的领域,甚至已经开始有一些以函数式编程作为主范式的框架出现,比如说cyclejs。\u003C\u002Fp\u003E\u003Cp\u003E那么,为什么函数式编程会如此重要呢?\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003E或许我们可以先从函数式编程的认识聊起。\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Ch2\u003E\u003Cb\u003E1 何为函数式编程?\u003C\u002Fb\u003E\u003C\u002Fh2\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003E1.1 什么是纯函数?\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003E让我们回想一下初中数学\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-a9ebcf5d8d900c02cc3ec2b2cc6f445f.png\& data-rawwidth=\&197\& data-rawheight=\&152\&\u003E\u003Cp\u003E我们知道一个函数有定义域和值域,对于定义域里面的每一个值,都会对应值域中唯一确定的一个值。\u003C\u002Fp\u003E\u003Cp\u003E这种函数的基本性质在编程里面却不一定成立,因为代码中的函数可能依赖于外部环境:\u003C\u002Fp\u003E\u003Ccode lang=\&js\&\u003Evar count = 0;\nfunction fnNotPure() {\n
if (count++ % 2) {\n
return 1;\n
} else {\n
return 2;\n
}\n}\n\nfnNotPure(); \u002F\u002F 0\nfnNotPure(); \u002F\u002F 1\u003C\u002Fcode\u003E\u003Cp\u003E正如上面的代码,函数依赖了外部变量count,导致相同的输入却得到不同的输出。\u003C\u002Fp\u003E\u003Cp\u003E在函数式编程有个概念称之为副作用(side effect),指的是函数的执行依赖于外部环境,这里的依赖可能是读取了外部变量,也可能是修改了外部变量。\u003C\u002Fp\u003E\u003Cp\u003E一个函数如果依赖外部环境,那么它的行为就会变得不可预测。因为你不知道外部环境什么时候会被改变,有句话貌似流传已久,很直接地指出了副作用给代码组织可能带来的问题:\u003C\u002Fp\u003E\u003Cblockquote\u003E\&Shared mutable state\u002Fvariable is the root of evil\&(可共享可修改的变量是所有罪恶的根源)\u003C\u002Fblockquote\u003E\u003Cp\u003E纯函数(pure function)的概念就是指没有副作用的函数,在理论上它等价于我们数学世界里面的函数概念。\u003C\u002Fp\u003E\u003Cp\u003E纯函数的优势在于,它向我们保证了其\&纯粹性\&,同样的输入无论调用多少次,都会是一样的输出,并且不用担心调用过程会修改外部环境。每个函数都是足够独立足够抽象的个体,我们可以放心地将函数进行组合(compose),这让我们在做代码复用或者重构时,不用去担心函数是否会影响到其他地方。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003E1.2 是什么而非怎么做\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003E函数式编程是声明式编程(declarative programming)的一种形式\u003C\u002Fb\u003E,你可能会联想到命令式编程(imperative programming)。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003E命令式编程\u003C\u002Fb\u003E通过编程语句声明了每一步的具体操作,如何修改变量以及按照什么样的顺序,这里强调的是\u003Cb\u003Ehow to\u003C\u002Fb\u003E。\u003C\u002Fp\u003E\u003Cp\u003E而函数式编程关注的点在于是需要哪些变量,需要什么样的操作,这里强调的是 \u003Cb\u003Ewhat is\u003C\u002Fb\u003E。\u003C\u002Fp\u003E\u003Cp\u003E直接看例子更容易理解:\u003C\u002Fp\u003E\u003Ccode lang=\&js\&\u003Elet arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];\n\n\u002F\u002F imperative let result = [];\nfor (let i = 0; i & arr. ++i) {\n
if (arr[i] % 2) {\n
result.push(arr[i] * arr[i]);\n
}\n}\n\n\u002F\u002F functional let oddItems = arr.filter(i =& i % 2);\nlet result = oddItem.map(i =& i * i);\u003C\u002Fcode\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E从上面的代码能看出一些问题来,对于命令式编程来讲,我们需要关注对数据的操作,如何创建数组,如何遍历元素,如何插入元素等等。\u003C\u002Fp\u003E\u003Cp\u003E而函数式编程写出来的东西更像是一系列声明语句:什么奇数,什么是平方。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003E很难说这两种编程范式哪种更好\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003E好纠结,纠结,纠结。。。\u003C\u002Fp\u003E\u003Cimg src=\&v2-956f74fda5d0d9f2d08fb.png\& data-rawwidth=\&449\& data-rawheight=\&299\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cul\u003E\u003Cli\u003E命令式编程符合人类的线性思维,首先做什么,然后做什么,步骤详细具体但稍显繁杂。同时也因为涉及到变量(状态)的共享和修改,在非线性(并行)计算里面,就会存在数据同步的问题。\u003C\u002Fli\u003E\u003Cli\u003E函数式编程抽象层次更好,代码编写和组织要求的门槛相对更高,不过其代码往往更能直接体现问题核心,同时对于并行计算有天然的支持。\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003E1.3 关注计算而非数据\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003E我们都知道对于冯诺依曼架构的计算机来讲,核心是存储和计算。这两个概念体现到编程中,分别就是数据以及对于数据的操作。\u003C\u002Fp\u003E\u003Cp\u003E编程范式也有相对应的,我们熟知的面向对象编程,关注点在于数据的抽象,如果你对大学的编程课还有印象的话,应该会知道,如果有一组固定的操作,为这些操作添加新的数据类型是很简单的事情。相对应的,函数式编程注重对数据的操作,在数据类型不变的情况下,想要添加新的计算方法很简单。相反如果要添加新的数据类型,那么你就不得不将大部分函数都进行修改。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Ch2\u003E\u003Cb\u003E2 前端中的函数式编程\u003C\u002Fb\u003E\u003C\u002Fh2\u003E\u003Cp\u003E函数式编程出现的时间很早,但在近几年才慢慢在前端中有所表现。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003E个人觉得主要原因在于,\u003C\u002Fb\u003E前端技术的快速发展虽然足以支持日益复杂的页面交互需求,但我们仍然需要一种能够更好表达交互的范式或者框架。\u003C\u002Fp\u003E\u003Cp\u003E而函数式编程在这方面还是挺适合前端的,列举我觉得\u003Cb\u003E最重要的两个点\u003C\u002Fb\u003E:\u003C\u002Fp\u003E\u003Cul\u003E\u003Cli\u003E前端麻烦的异步问题,可以由函数式编程中的异步计算来解决;\u003C\u002Fli\u003E\u003Cli\u003E声明式编程基本被业界证明是前端UI编程的一种最佳实践方式。\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cp\u003E当然这些共性问题已经被发现了,前端领域有很多特性、库或者框架来支持和应用函数式编程。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003E2.1 函数式语言\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003E有很多语言都是支持函数式编程的,当然我们的 JavaScript也支持。\u003C\u002Fp\u003E\u003Cp\u003E一门高级语言是否支持函数式编程,只要看其函数\u003Cb\u003E是否是一等公民\u003C\u002Fb\u003E(first class):函数能够作为其他函数的参数或者返回值。\u003C\u002Fp\u003E\u003Cp\u003E这个概念其实跟高阶函数(higher-order function)差不多,只是两者描述的主体不一样。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003EJavaScript中的语法\u003C\u002Fb\u003E:\u003C\u002Fp\u003E\u003Ccode lang=\&js\&\u003Efunction fnAsArgument(fn) {\n
return fn();\n}\n\nfunction fnAsResult() {\n
return function() {\n
\u002F\u002F function body\n
};\n}\u003C\u002Fcode\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003E2.2 Promise\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003EPromise从 es6 开始成为JavaScript原生支持的语法,大家也应该不会陌生。作为处理异步的一种方式,它的特点在于通过将异步操作封装起来,让你可以像操作同步代码一样去进行操作:\u003C\u002Fp\u003E\u003Ccode lang=\&js\&\u003Evar asyncTwo = new Promise(function(resolve){\n
setTimeout(function() {\n
resolve(2)\n
}, 0)\n});\n\nfunction addThree(value) {\n
return Promise.resolve(value + 3);\n}\n\nfunction multipleTwo(value) {\n
return Promise.resolve(value * 2);\n}\n\nasyncTwo.then(addThree).then(multipleTwo).then(console.log); \u002F\u002F 10\u003C\u002Fcode\u003E\u003Cp\u003E如果你对函数式编程熟悉的话,可能会意识到 promise 其实就是一种 monad。函数式编程中对于monad有一整套完善的操作,可以将异步函数和同步函数统一起来,完美地支持函数的组合。目前已经有类似的库来完成封装,比如RxJS,xstream 等。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003E2.3 主流框架与函数式编程\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003E目前主流的前端框架,比如vue,react,大部分都是支持函数式编程的,甚至已经开始有一些以函数式编程为主范式的框架开始出现,比如 cyclejs,turbine。\u003C\u002Fp\u003E\u003Cp\u003E这里有必要讲一下主流框架为何支持函数式编程。\u003C\u002Fp\u003E\u003Cp\u003E在为了满足更多样化的需求的同时,前端页面变得越来复杂。\u003C\u002Fp\u003E\u003Cp\u003E页面视图从最开始的静态页面,到服务端动态渲染,再到前端渲染。而渲染过程实际上是数据到视图的一种映射,传统的基于 DOM API直接操作视图的方式,在前端渲染时开始显得很麻烦,一个主要的原因在于命令式编程的抽象层次不够高,它将dom操作的细节完全暴露给开发者,所以很难建立直观、有效的映射关系。\u003C\u002Fp\u003E\u003Cp\u003E前端开发者更希望有一种所见即所得的编程方式,可以完全将如何操作dom、如何更新dom等工作隔离开来,只要关注最核心的部分,数据和视图的映射关系。\u003C\u002Fp\u003E\u003Cp\u003E幸运的是,前端模板技术的 快速发展满足了这个需求,比如react中使用了 jsx 来作为抽象视图层:\u003C\u002Fp\u003E\u003Ccode lang=\&js\&\u003E\u002F\u002F imperative\nfunction render(data) {\n
$(\&div & h1\&).html(data.title);\n
$(\&div & p\&).html(data.content);\n}\n\n\u002F\u002F declarative\nfunction render(data) {\n
\u002F\u002F jsx syntax\n
return (\n
&h1&{ data.title }&\u002Fh1&\n
&p&{ data.content }&\u002Fp&\n
&\u002Fdiv&\n
);\n}\u003C\u002Fcode\u003E\u003Cp\u003E抽象视图层的引入打开了前端函数式编程的大门,正如上面的例子,第二个render就是一个纯函数,它帮我们隐藏操作dom的细节(渲染实际上是一种副作用),只保留了最纯粹的映射关系。从因果关系上来讲,很难说是因为函数式编程才引入了抽象视图层,而应该是抽象视图层的选择反而无意中促进了函数式编程在前端中的应用。\u003C\u002Fp\u003E\u003Cp\u003E其实正如第2部分一开始讲的,函数式编程本身的异步处理、声明式等特性是很适合前端开发的,所以才导致前端技术发展过程中,多多少少有点向函数式编程靠近、借鉴的原因。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003E2.4 一种新的编程范式 – FRP\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003E最后再讲一种以函数式编程为基础引申出更具体的编程范式:\u003C\u002Fp\u003E\u003Cp\u003E响应式函数编程(FRP,Functional Reactive Programming)。\u003C\u002Fp\u003E\u003Cp\u003E目前已经有基于这种范式而开发的库或者框架,比如RxJS、cyclejs、turbine等。\u003C\u002Fp\u003E\u003Cp\u003E要理解 FRP 其实很简单,函数式编程的概念在第1部分大致讲了,响应式编程的介绍不细讲,有兴趣可以看这篇文章:The introduction to Reactive Programming you've been missing。\u003C\u002Fp\u003E\u003Cp\u003E这里简单讲响应式编程跟函数式编程的关系。\u003C\u002Fp\u003E\u003Cp\u003E首先重新讲一下函数式编程的基础,即纯函数。我们知道纯函数不会对外部环境造成影响,那么问题来了:\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-3799988deaf90e1f53d60db62f09be97.png\& data-rawwidth=\&323\& data-rawheight=\&313\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003E假设\u003C\u002Fb\u003E我们写了一堆纯函数,并完美地将它们

我要回帖

更多关于 不尽人意 的文章

 

随机推荐