龙空技术网

前后端全部用 JS 开发是什么体验(Hybrid + Egg.js经验分享)上

Echa攻城狮 572

前言:

当前兄弟们对“jsjquery深拷贝”大致比较珍视,兄弟们都需要学习一些“jsjquery深拷贝”的相关文章。那么小编在网上网罗了一些对于“jsjquery深拷贝””的相关文章,希望你们能喜欢,你们快快来学习一下吧!

作者 | Derek Yeung

编辑 | Nodejs技术栈

转发链接:

目录

前后端全部用 JS 开发是什么体验(Hybrid + Egg.js经验分享)上 本篇

前后端全部用 JS 开发是什么体验(Hybrid + Egg.js经验分享)中

前后端全部用 JS 开发是什么体验(Hybrid + Egg.js经验分享)下

Class 前言

最近刚从公司离职,回顾下来之前项目中除了代码,关心更多的是业务,一直没好好梳理整个技术栈,趁着现在时间自由,打算好好梳理总结一下,算是给过去几年的node生涯交一份答卷,也顺便给大家分享一下前后端全部用javascript开发是什么样的体验

Class 项目背景

项目是一款面向学校、教师、家长以及学生的教育类app,涉及的平台有:

iosandroidpc webh5 web

使用的技术栈有:

For 前端:html5plus + moe-plus + vueFor 后端:egg + egg-moeClass 前端

既然项目是一个app应用,那么第一个问题就是:原生 or 混合

原生和混合的优劣势,我相信大家都清楚,不引战不讨论,这里就不多说了

对于我们而言,在项目初期我们并没有对应两个端的开发人员,并且初期的版本需要大量的迭代和测试,所以在这种条件下采用原生开发是不合适的

那么,第二个问题就是要确定混合开发的方案,项目立项是在2016年,那么可以来看一下,在当时我们可以选择的方案有哪些:

Html5 PlusReact NativeWeexFlutter其他移动框架 + 套壳(理论上也是一种方案...)

Weex和Flutter在当时属于新生儿,各自的生态圈还不够完善,所以没有继续考虑

剩下的5+和React各有千秋,不过最终还是选择了5+,原因其实也很简单:方便(图省事)

熟悉HB的同学应该清楚,5+在打包方面是秒杀其他方案的,够简单也够省事,不用对环境进行配置,也不需要各种命令行build,当然这一切也是建立在5+稳定的情况下

那么5+是不是就是最佳的方案了呢?

当然不是,5+在打包方面可以说是最佳的,但是在开发和调试上简直就是噩梦

比如理想中的场景是在PC浏览器中预览并且调试,但是现实情况是只要是需要调用plus的地方,只 能 真 机...

没有所见即得所得,也没有F12,只有一条数据线和一个只能打印String的控制台...

现在回想起来,唯一让我坚持下去的理由大概就是自己选的框架,跪着也要啃完

终于在18年的时候,我决定对架构动刀,彻底解决这个问题

恰巧当时5+的兄弟uni-app也诞生了,但是刚出生的uni-app是否稳定,是否能持续下去都是一个未知数,我也不敢贸然采用一个全新的方案

并且此时app已经稳定,更换框架已经不单纯是面临学习成本的问题,更多的是风险

所以经过大概两周的测试,最终我选择在5+的基础上搭建一层中间件,用来解决这个问题,于是moe-plus就诞生了

About moe-plus

moe-plus是一个基于vue和5+开发的中间层 moe-plus的定位不是框架,moe-plus更像是一个定制开发的“翻译官”,通过调用统一的api,实现跨平台的效果

moe-plus目录结构:

├─ components│  ├── webview│  └── request├─ core├─ runtimes│  └── html5plus│      └── components│          ├── webview│          └── request│  └── vue│      └── components│          ├── webview│          └── request

moe-plus规范了每个component的api,在不同环境api有差异的情况下,通过runtime内部的component来覆盖默认的component,

以最常见的页面跳转举例:

this.Page.open(path, param);

在vue环境下执行:

open(path, param = {}, meta = {}) {  const vm = plus.vm;  const prod = process.env.NODE_ENV === 'production';  const hashMode = ~window.location.href.indexOf('#');  if (!prod) {    const json = {      param: JSON.stringify(param)    };    const stringified = queryString.stringify(json);    const isHasParam = path.indexOf('?') > -1 ? true : false;    if (hashMode) {      path = path + (isHasParam ? '&' : '?') + stringified;    }  }  this.clearWebview();  if (vm) {    vm.isForwarding = true;    vm.$router.push({      path,      query: param,      meta: meta    });  }}

在5+环境下则会执行:

open(path, param) {  return this.openView(path, param, 'slide-in-right');}openView(path, param = {}, animate = '', extras = {}) {  const url = this.loadUrl(path);  let webview = this.exists(path);  if (param && param.isInvoke) {    extras.isInvoke = true;  }  const extend = Object.assign({}, extras, {    param,  });  const onPageShowed = () => {    this.trigger(webview, 'page-show-success');  };  if (!webview) {    const styles = param.styles || {};    webview = this.create(path, url, Object.assign({      top: 0,      bottom: 0,    }, styles), extend);    const paramWatcher = `window.addEventListener('param-change', function(listener) {      var webview = plus.webview.currentWebview();      var param = listener.detail;      if (!(param instanceof Object)) {        param = JSON.parse(param);      }      webview.param = param;    });`;    let loading = null;    const timer = setTimeout(() => {      loading = Toast.showWaiting('载入中...');    }, isIos ? 600 : 600);    webview.addEventListener('loaded', () => {      if (timer) {        clearTimeout(timer);      }      if (loading) {        loading.close();      }      webview.evalJS(paramWatcher);      webview.show(animate, Duration, onPageShowed);    });  } else {    this.trigger(webview, 'param-change', param);    this.trigger(webview, 'page-open');    this.trigger(webview, 'page-show');    webview.show(animate, Duration, onPageShowed);  }  return webview;}

在H5页面中,moe-plus调用的是vue-router,而在app中则是5+的webview,其他component也是同理

同时moe-plus也提供了环境判断的接口,可以在一些有硬性差异的部分手动判断,分别实现不同的效果

前面也提到了,moe-plus并不是从框架的角度去开发的,所以也并没有开源的计划,这里只做为一个案例分享,如果有感兴趣的小伙伴可以自行下载该仓库体验

另外毕竟现在0202年了,uni-app也已经成熟,对于有多端需求的小伙伴我的推荐是直接上手uni-app(dcloud打钱),虽然无法达到100%全端适配,但也是目前来讲多端里最好的方案了

当然了,如果只是追求H5和App两个端的体验的话,那么我的推荐是RN或者类moe-plus的方案

About 项目

前面花了不少篇幅向大家介绍了moe-plus,下面就给大家分享一下我们的项目开发日常和一些小工具

首先我们来说说环境配置那些事

不管是什么样的项目,都会遇到同样一个问题,那就是环境配置

每一个项目都会有prod或者dev环境,有的项目还会有更多(如beta、test)

我们在开发过程中经常会遇到需要切换不同的环境,那么不同的环境该如何切换呢?

Plan A:

const config = {  devConfig};const config = {  prodConfig};  

这是最简单的做法,缺点也很明显,代码有污染,切换容易出错,可变的配置项越多就越复杂

Plan B:

const env = require(envFilePath) // process.env.NODE_ENV;const envConfigPath = `${env}.conf`;cosnt config = require(envConfigPath);

这个是我们采用的方案,应该也是最普遍的做法,通过env文件或环境变量,读取不同的配置文件

这个方案没有什么问题,但当环境越来越多,配置变更越来越多的时候,则需要记住越来越多的env

针对这种情况,我们内部设计一套内部的cli工具,用来创建/管理配置信息

通过npm run config生成配置文件

生成配置文件之后通过npm run dev/build来选择当前编译环境,配置文件默认随环境选择,亦可手动选择

同时使用electron配套搭配了可视化界面

可视化界面中更加清晰的显示了每个项目的状态

细心的小伙伴应该注意到了上图中还有一个远程调试二维码

这也是我们在开发过程中做的一个功能

在项目开发的过程中,难免会遇到测试的问题,有的时候哪怕是很微小的变动,往往也需要发布一个版本

如果测试团队是异地测试,更新包也有公网泄露的风险

我们解决这个问题的方法是:

我们将内网与外网打通,让外部能够访问内网的开发机,异地预览实时的效果

打包好的更新包通过加密上传到服务器/oss,通过扫码授权更新

同时记录下每个包的版本信息,方便测试人员在不同的版本之间进行切换

最后将运行/发布的版本的二维码提供给相应的测试人员,测试人员通过App扫码即可远程更新

远程调试演示:

安装好的开发版本也会自动连接到远程日志服务器,将App运行过程中产生的console实时传递到开发人员一端

除了扫码远程调试之外,我们也做了扫码登录

用户在扫码之后服务器会建立一条通道,当用户确认登录之后服务器会下发新授权令牌到授权网页,页面中则是通过postMessage进行通信,使得原网页拿到对应的token

由于产品的定位是平台,那么必然也少不了与第三方应用进行交互

我们给第三方提供了用户的Oauth授权以及敏感信息授权(比如手机号)

第三方通过对接公共平台,调用我们的js sdk可以轻松接入到我们的系统

前端的部分,就先讲到这里,虽然有一些功能因为业务的原因没有办法拿出来讲,不过相信通过上面的几个演示大家也可以看到从感知度来讲,混合方案与原生并无很大差异,更多的是性能上的差异(主要存在于低端机型),不过我相信在未来配置越来越高的情况下,这种差异会逐渐变小甚至无感

Class 后端

说完前端,接下来就说一说后端那些事

在2017年之前整个系统并不完全是由node支撑的,核心业务部分是"almost世界上最好的语言"php开发的

因为前端部分也是h5的混合开发方案,所以切换成node其实更多的原因是想体验一下用一种语言统一前后端的感觉,顺便挑战一下只招js工程师的成就

虽然想法很美好,奈何现实给了我一拳

由于是大规模替换,如果要将所有的代码进行重写那将耗费非常多的时间,为了减少重构的时间,我选择的是基于Koa重建yii2(还是图省事)

结果就是带来了《由一行代码引发的“血案”》

感兴趣的小伙伴可以进传送门: 这次事故之后我们也彻底放弃了偷懒的做法,选择拥抱egg的怀抱(真香)

之所以选择egg,是因为我们需要一套有自己内部规范并且可靠的框架,而egg所提供的插件开发和框架开发恰好就是我们需要的

我们的业务涉及到前台、后台、鉴权、支付、三方服务、socket等等大大小小14个平台

每个平台中既有独立的业务,也有公共的部分,所以我们在egg的基础上研发了自己的framework:egg-moe

About egg-moe

egg-moe通过egg的扩展loader功能,将common目录下的service、model和config进行挂载

将所有公共部分业务全部放到common下,平台私有业务放在各自目录

目录结构:

├─ common│  └── service│      └── common.js│      └── ...│  └── model│      └── common.js│      └── ...├─ frontend│  └── service│      └── frontend-custom.js│      └── ...│  └── model│      └── frontend-custom.js│      └── ...├─ backend│  └── service│      └── backend-custom.js│      └── ...│  └── model│      └── backend-custom.js│      └── ...

这样在开发过程中,涉及到公共部分的业务由common统一接口,每个业务下只需要关注自己本身的业务

同时,egg-moe统一了路由规范,统一错误捕获,另外把项目中常用的module整合到了一起,避免各自调用不同的module

虽然这里不想讨论其他的框架,但是这里不得不说一句,如果你的团队需要的是一套符合自己内部规范的框架,那么通过egg+定制框架的方式一定是最佳的

当然了,没有最完美的框架,只有最合适的框架,根据项目情况选择最合适的框架才是真理

About 后端

在后端开发上,因为涉及到业务的原因,很少能有具体的功能拿来讲,这里主要就和大家分享一下我们平台的架构和我们在过程中开发的一些插件

平台架构:

前台、后台、Socket三个服务分别为面向客户和管理一端的前置服务,负责接收处理有人为操作的请求

统一服务是面向业务层面的后置服务,负责统一接口、鉴权、清洗数据等任务

第三方服务与公共平台则是负责与第三方数据交互以及我们对外开放接口部分

Oauth则是用户与第三方之间建立授权的核心服务,所有第三方与用户之间的关系均由该服务进行处理

Schedule顾名思义,我们的计划任务服务,负责90%的计划任务,剩下的10%则为各个模块内部任务

Slave这个服务就比较惨了,什么脏活累活都是他干,属于名副其实的slave...

在学校端,我们还有一部分业务系统,但这部分与平台其实没什么关系,后面也会讲到一部分,这里就不列出了

目前的配置是9台ecs + 4个mysql节点+2slave节点 + 1redis

部署方面没有采用容器而是传统方案,运维和监控方面则是完全交给了alinode

About 插件

egg-database egg-database是一个orm插件,之所以没有选择sequelize而是新造了轮子主要的原因还是习惯了yii的风格,所以参考了yii的风格来实现了node版本,熟悉yii的同学应该对下面的代码不陌生

在egg-database中,我们这样定义模型

app/model/user.js

'use strict';const { ActiveRecord, Validate, Field } = require('moe-query');const { Rule } = Validate;module.exports = app => {  class Model extends ActiveRecord {    extras() {      return {};    }    tableName() {      return 'user';    }  }  const model = new Model();  model.fields({    name: new Field('name').label('昵称'),    mobile: new Field('mobile').label('手机号').mobile()      .required(),    password: new Field('password').label('密码').string(128)      .required(),    last_login_time: new Field('last_login_time').label('上次登录时间').time(),    update_time: new Field('update_time').label('更新时间').time(),    create_time: new Field('create_time').label('创建时间').time(),  });  // 或简写模式:model.fields([ 'name', 'mobile', ... ]);  model.rules([    new Rule(model.Fields.mobile),    new Rule(model.Fields.password),  ]);  if (app.rule.Time) {    model.mount(app.rule.Time);  }  return model;};

查询单条数据:

const model = this.ctx.model.User;// 1const user = await model.fetch(123);// 2const user = await model.fetch('张三', 'name');// 3const user = await model.fetch({  name: '张三',  password: '李四'});

查询多条数据:

const model = this.ctx.model.User;const all = await model.query.where([  [ 'name', '=', '张三' ]]).desc().all(); /* or asc(), order()*/// 分页const list = await model.query.where([  [ 'name', '=', '张三' ]]).list(/* request */);// 排序 分组const datas = await model.query.where([  [ 'name', '=', '张三' ]]).limit(20).offset(0).order('id', 'desc').group('name').all();

新增数据:

const model = this.ctx.model.User;const user = model.create();user.name = '张三';user.password = '李四';await user.save();// or const user = model.create({  name: '张三',  password: '李四'});await user.save();// orconst user = model.create();await user.save({  name: '张三',  password: '李四'});

修改数据:

const model = this.ctx.model.User;const user = await user.fetch(1); // 得到张三user.name = '王五';await user.save();// orawait model.query.update({  name: '王五'}, 1 /* or [where Condition] */);

关联查询:

const model = this.ctx.model.User;const list = await model.query.leftJoin('table', [ /* join Condition*/ ]);

除了关联查询之外,同样也提供关系查询

首先我们添加一个与user相关的model,这里以user device举例

app/model/user/device.js

'use strict';const { ActiveRecord, Validate, Field } = require('moe-query');const { Rule } = Validate;module.exports = app => {  class Model extends ActiveRecord {    tableName() {      return 'user_device';    }  }  const model = new Model();  model.fields([    'userid',    'name',    'token',    'update_time',    'create_time'  ]);  if (app.rule.Time) {    model.mount(app.rule.Time);  }  return model;};

然后在 app/model/user.js 的Model中添加relation和对应的extras

...  relation() {    return {      // hasOne or hasMany      Device: this.hasMany(app.model.User.Device, {        id: 'userid',      })    };  }  extras() {    return {      devices() {        return this.Device || [];      }    };  }  ...

最后在查询时,通过joinWith带入

const model = this.ctx.model.User;const list = await model.query.joinWith('Device').all();

另外,model也提供了各个阶段的查询事件,如before save/after save等等

比如通过 model.on('before save'); 可以在数据保存前做最后的处理, 通过 model.on('after save'); 则是在数据保存后得到对应的事件

同时egg-database也提供了规则的概念(Rule),可以将重复、公共部分的事件处理成规则

比如上面model中model.mount(app.rule.Time)的部分,具体的实现是这样的:

'use strict';const { ActiveRecord, Validate, Field } = require('moe-query');const { Rule } = Validate;const CreateAttribute = 'create_time';const UpdateAttribute = 'update_time';const TimeRule = new Rule('time:save');TimeRule.inject(function(query) {  const isNew = this.is('exists'); // 判断是新增还是更新,true为新增,false为更新  const time = Math.round(new Date().getTime() / 1000);  if (!isNew || !this[CreateAttribute]) { // 如果是新增并且有创建时间字段,设置该字段为当前时间    if (this.Fields[CreateAttribute]) {      this[CreateAttribute] = time;    }  }  if (this.Fields[UpdateAttribute]) { // 如果是有更新时间字段,设置该字段为当前时间    this[UpdateAttribute] = time;  }  return true;}).on([ 'before save' ]);module.exports = TimeRule;

通过该rule实现了数据创建时间、更新时间的自动设置,业务中再也不需要手动指定创建/更新时间

通过事件和rule的配合,我们还可以做一些更加灵活的事情

本篇未完结,请见下一篇

推荐JavaScript经典实例学习资料文章

《一文带你搞懂 babel-plugin-import 插件(上)「源码解析」》

《一文带你搞懂 babel-plugin-import 插件(下)「源码解析」》

《JavaScript常用API合集汇总「值得收藏」》

《推荐10个常用的图片处理小帮手(上)「值得收藏」》

《推荐10个常用的图片处理小帮手(下)「值得收藏」》

《JavaScript 中ES6代理的实际用例》

《12 个实用的前端开发技巧总结》

《一文带你搞懂搭建企业级的 npm 私有仓库》

《教你如何使用内联框架元素 IFrames 的沙箱属性提高安全性?》

《细说前端开发UI公共组件的新认识「实践」》

《细说DOM API中append和appendChild的三个不同点》

《细品淘系大佬讲前端新人如何上王者「干货」》

《一文带你彻底解决背景跟随弹窗滚动问题「干货」》

《推荐常用的5款代码比较工具「值得收藏」》

《Node.js实现将文字与图片合成技巧》

《爱奇艺云剪辑Web端的技术实现》

《我再也不敢说我会写前端 Button组件「实践」》

《NodeX Component - 滴滴集团 Node.js 生态组件体系「实践」》

《Node Buffers 完整指南》

《推荐18个webpack精美插件「干货」》

《前端开发需要了解常用7种JavaScript设计模式》

《浅谈浏览器架构、单线程js、事件循环、消息队列、宏任务和微任务》

《了不起的 Webpack HMR 学习指南(上)「含源码讲解」》

《了不起的 Webpack HMR 学习指南(下)「含源码讲解」》

《10个打开了我新世界大门的 WebAPI(上)「实践」》

《10个打开了我新世界大门的 WebAPI(中)「实践」》

《10个打开了我新世界大门的 WebAPI(下)「实践」》

《「图文」ESLint 在中大型团队的应用实践》

《Deno是代码的浏览器,你认同吗?》

《前端存储除了 localStorage 还有啥?》

《Javascript 多线程编程​的前世今生》

《微前端方案 qiankun(实践及总结)》

《「图文」V8 垃圾回收原来这么简单?》

《Webpack 5模块联邦引发微前端的革命?》

《基于 Web 端的人脸识别身份验证「实践」》

《「前端进阶」高性能渲染十万条数据(时间分片)》

《「前端进阶」高性能渲染十万条数据(虚拟列表)》

《图解 Promise 实现原理(一):基础实现》

《图解 Promise 实现原理(二):Promise 链式调用》

《图解 Promise 实现原理(三):Promise 原型方法实现》

《图解 Promise 实现原理(四):Promise 静态方法实现》

《实践教你从零构建前端 Lint 工作流「干货」》

《高性能多级多选级联组件开发「JS篇」》

《深入浅出讲解Node.js CLI 工具最佳实战》

《延迟加载图像以提高Web网站性能的五种方法「实践」》

《比较 JavaScript 对象的四种方式「实践」》

《使用Service Worker让你的 Web 应用如虎添翼(上)「干货」》

《使用Service Worker让你的 Web 应用如虎添翼(中)「干货」》

《使用Service Worker让你的 Web 应用如虎添翼(下)「干货」》

《前端如何一次性处理10万条数据「进阶篇」》

《推荐三款正则可视化工具「JS篇」》

《如何让用户选择是否离开当前页面?「JS篇」》

《JavaScript开发人员更喜欢Deno的五大原因》

《仅用18行JavaScript实现一个倒数计时器》

《图文细说JavaScript 的运行机制》

《一个轻量级 JavaScript 全文搜索库,轻松实现站内离线搜索》

《推荐Web程序员常用的15个源代码编辑器》

《10个实用的JS技巧「值得收藏」》

《细品269个JavaScript小函数,让你少加班熬夜(一)「值得收藏」》

《细品269个JavaScript小函数,让你少加班熬夜(二)「值得收藏」》

《细品269个JavaScript小函数,让你少加班熬夜(三)「值得收藏」》

《细品269个JavaScript小函数,让你少加班熬夜(四)「值得收藏」》

《细品269个JavaScript小函数,让你少加班熬夜(五)「值得收藏」》

《细品269个JavaScript小函数,让你少加班熬夜(六)「值得收藏」》

《深入JavaScript教你内存泄漏如何防范》

《手把手教你7个有趣的JavaScript 项目-上「附源码」》

《手把手教你7个有趣的JavaScript 项目-下「附源码」》

《JavaScript 使用 mediaDevices API 访问摄像头自拍》

《手把手教你前端代码如何做错误上报「JS篇」》

《一文让你彻底搞懂移动前端和Web 前端区别在哪里》

《63个JavaScript 正则大礼包「值得收藏」》

《提高你的 JavaScript 技能10 个问答题》

《JavaScript图表库的5个首选》

《一文彻底搞懂JavaScript 中Object.freeze与Object.seal的用法》

《可视化的 JS:动态图演示 - 事件循环 Event Loop的过程》

《教你如何用动态规划和贪心算法实现前端瀑布流布局「实践」》

《可视化的 js:动态图演示 Promises & Async/Await 的过程》

《原生JS封装拖动验证滑块你会吗?「实践」》

《如何实现高性能的在线 PDF 预览》

《细说使用字体库加密数据-仿58同城》

《Node.js要完了吗?》

《Pug 3.0.0正式发布,不再支持 Node.js 6/8》

《纯JS手写轮播图(代码逻辑清晰,通俗易懂)》

《JavaScript 20 年 中文版之创立标准》

《值得收藏的前端常用60余种工具方法「JS篇」》

《箭头函数和常规函数之间的 5 个区别》

《通过发布/订阅的设计模式搞懂 Node.js 核心模块 Events》

《「前端篇」不再为正则烦恼》

《「速围」Node.js V14.3.0 发布支持顶级 Await 和 REPL 增强功能》

《深入细品浏览器原理「流程图」》

《JavaScript 已进入第三个时代,未来将何去何从?》

《前端上传前预览文件 image、text、json、video、audio「实践」》

《深入细品 EventLoop 和浏览器渲染、帧动画、空闲回调的关系》

《推荐13个有用的JavaScript数组技巧「值得收藏」》

《前端必备基础知识:window.location 详解》

《不要再依赖CommonJS了》

《犀牛书作者:最该忘记的JavaScript特性》

《36个工作中常用的JavaScript函数片段「值得收藏」》

《Node + H5 实现大文件分片上传、断点续传》

《一文了解文件上传全过程(1.8w字深度解析)「前端进阶必备」》

《【实践总结】关于小程序挣脱枷锁实现批量上传》

《手把手教你前端的各种文件上传攻略和大文件断点续传》

《字节跳动面试官:请你实现一个大文件上传和断点续传》

《谈谈前端关于文件上传下载那些事【实践】》

《手把手教你如何编写一个前端图片压缩、方向纠正、预览、上传插件》

《最全的 JavaScript 模块化方案和工具》

《「前端进阶」JS中的内存管理》

《JavaScript正则深入以及10个非常有意思的正则实战》

《前端面试者经常忽视的一道JavaScript 面试题》

《一行JS代码实现一个简单的模板字符串替换「实践」》

《JS代码是如何被压缩的「前端高级进阶」》

《前端开发规范:命名规范、html规范、css规范、js规范》

《【规范篇】前端团队代码规范最佳实践》

《100个原生JavaScript代码片段知识点详细汇总【实践】》

《关于前端174道 JavaScript知识点汇总(一)》

《关于前端174道 JavaScript知识点汇总(二)》

《关于前端174道 JavaScript知识点汇总(三)》

《几个非常有意思的javascript知识点总结【实践】》

《都2020年了,你还不会JavaScript 装饰器?》

《JavaScript实现图片合成下载》

《70个JavaScript知识点详细总结(上)【实践】》

《70个JavaScript知识点详细总结(下)【实践】》

《开源了一个 JavaScript 版敏感词过滤库》

《送你 43 道 JavaScript 面试题》

《3个很棒的小众JavaScript库,你值得拥有》

《手把手教你深入巩固JavaScript知识体系【思维导图】》

《推荐7个很棒的JavaScript产品步骤引导库》

《Echa哥教你彻底弄懂 JavaScript 执行机制》

《一个合格的中级前端工程师需要掌握的 28 个 JavaScript 技巧》

《深入解析高频项目中运用到的知识点汇总【JS篇】》

《JavaScript 工具函数大全【新】》

《从JavaScript中看设计模式(总结)》

《身份证号码的正则表达式及验证详解(JavaScript,Regex)》

《浏览器中实现JavaScript计时器的4种创新方式》

《Three.js 动效方案》

《手把手教你常用的59个JS类方法》

《127个常用的JS代码片段,每段代码花30秒就能看懂-【上】》

《深入浅出讲解 js 深拷贝 vs 浅拷贝》

《手把手教你JS开发H5游戏【消灭星星】》

《深入浅出讲解JS中this/apply/call/bind巧妙用法【实践】》

《手把手教你全方位解读JS中this真正含义【实践】》

《书到用时方恨少,一大波JS开发工具函数来了》

《干货满满!如何优雅简洁地实现时钟翻牌器(支持JS/Vue/React)》

《手把手教你JS 异步编程六种方案【实践】》

《让你减少加班的15条高效JS技巧知识点汇总【实践】》

《手把手教你JS开发H5游戏【黄金矿工】》

《手把手教你JS实现监控浏览器上下左右滚动》

《JS 经典实例知识点整理汇总【实践】》

《2.6万字JS干货分享,带你领略前端魅力【基础篇】》

《2.6万字JS干货分享,带你领略前端魅力【实践篇】》

《简单几步让你的 JS 写得更漂亮》

《恭喜你获得治疗JS this的详细药方》

《谈谈前端关于文件上传下载那些事【实践】》

《面试中教你绕过关于 JavaScript 作用域的 5 个坑》

《Jquery插件(常用的插件库)》

《【JS】如何防止重复发送ajax请求》

《JavaScript+Canvas实现自定义画板》

《Continuation 在 JS 中的应用「前端篇」》

作者 | Derek Yeung

编辑 | Nodejs技术栈

转发链接:

标签: #jsjquery深拷贝 #js案例100实战 #yii2引入js