深拷贝/浅拷贝
Object.assign可以实现浅拷贝
后面突然想起来的,追加的时候追忘了说的啥
原生对象
应该是高程,对js对象分原生对象和引用对象
说道原生对象我容易忽略object与function
对象
面向对象三大特点,继承,多态与封装
其中
继承
指子类继承父类的特征和行为,使得子类对象具有父类的实例域和方法,以达到复用的目的,是面向对象语言的基础特性
在面向对象被市场接收后,js选择了强行面向对象也就是依赖原型链的继承方式
原型链继承实现
针对面向原型链继承的验证包括几个关键函数
constructor,__proto__,prototype
原型链继承验证
针对验证,主要几个关键函数
isPrototypeOf,hasOwnProperty,instanceof,typeof
甚至包括ie系列的isXXX系列
原型链继承验证bug
但问题在于,整体的验证体系
1.属于鸭辨式
2.两套体系,主要指ie的isXXX系列
即使到了Object.prototype.toString为止,也没有填完原型链继承验证的坑
(主要指宿主环境下的对象,跨域下对象比较(这个类似与不同jvm解析对对象是否相同))
原型链继承使用
最后在使用上,基于原型链继承实现,除了框架上的挂载外,很少主动使用
比如vue任意组件内部的this依次继承VueComponent,Vue,允许this可直接使用Vue挂载的其他实例
但是诸如其他热插拔式的组件,则不会继承Vue,比如VueRouter等
大部分组件库或框架也是如此,比如jq组件,可以通过this使用jq的实例化方法
但也仅限于此,更多的方式使用组合,本身继承就有属性污染或者非必要的创建等很已被证明的冗余的内容(这里尤其值java,以及java针对继承爆炸所设计的23中设计模式)
针对ui组件库,一般都不会选择完整的继承,类似于混合或者阉割的概念,无视对象的判断,更像是依赖原型链进行复用的技巧,比如easyui
$.fn.combobox = function(options, param){
if (typeof options == 'string'){
var method = $.fn.combobox.methods[options];
if (method){
return method(this, param);
} else {
//此处this指向的是jquery的实例,而非是combobox的实例,如果按照任意一款关于js原型链继承的demo去参考,这里绝对会怪怪的
return this.combo(options, param);
}
}
}
原型链继承使用原则
结合prototype.js针对原生原型拓展的槽点,我一般会表达如下意思
1.自己给自己开发的可以继承
2.使用别人开发的只能组合
除非打补丁,或者有特定的解决方案,比如Ext
因为本身并非强类型语言,所以这块问题不大
js继承的替代
继承是为了代码复用,而代码复用的方式有两种
1.组合
2.继承
而组合一定会谈到的就是闭包
闭包
针对闭包,引用犀牛书,包含两个部分
1.函数对象通过作用域进行关联
2.函数体内变量保存在函数作用域内
简单地说的确是解决变量污染
但问题是,闭包来自词法作用域,这个是现代编程语言都有的,他不算js的特性
我想描述的意思应该是闭包可以实现代码复用,代替继承
url执行发生了什么
这是我做java的时候做的总结
1.浏览器请求前过滤-- 每个浏览器并不相同,可能会包括
- 补全url
如默认http协议,最后的字符串/去掉
- 验证url
如非法协议会转换为http
- 没有网,直接默认404
- 脱机/离线数据
- 网址关联
- 地址记录
- 安全警告
等等基于体验或恶意的功能,最后是发送请求
2.发送请求
- DNS解析
- TCP连接
- 请求
- 反馈【浏览器处理请求头】
比如根据后台传递类型与系统关联,以进行下一步比如自带浏览器下载,第三方插件如迅雷下载或页面加载
- 内容传输
- 连接结束
- 处理内容
1.包括进行关联,如自动打开word,图片等,国企常见需求,通过url请求,自动打开某软件
2.检测同源协议,主要指xhr,通过浏览器一般没有
3.自定义的如页面渲染
- 第三方插件,比如chrome下的json美化
- 也可能有恶意拦截或恶意劫持,参考360
内存泄漏与溢出
描述
无用内存无法释放
实例
经常在jq的两套数据缓存上进行对比,jq针对数据缓存,即
$.fn.setData()
为什么要修改实现
代码规范
操作上一般强调,减少对dom的数据挂载
vue订阅与发布
Object.defineProperty,配合代理,拦截array系列各种增删改函数,针对对象属性的新增与删除使用$set,这个在2.0应该依然没有变化
更早的angular,avalon则会使用脏检
vue不监听对象的新增
对接接口的时候,后台有次优化,针对所有undefined数据,全部不返回,以节省流量,所以所有对象,全都要求有默认数据(两组默认数据,一类正常的默认数据,以面对产品,一类为数据结构,属性为undefined,以面对后台),而后通过Object.assign的方式一层层进行修正,本身考虑过如何让vue监听对象属性的新增或者在操作是使用Vue.set的方式进行动态添加
而Vue3则会计划使用new Proxy来重新进行实现
所以对vue不能监听数据的属性新增比较执念
vue组件内props与data命名冲突
因为后台语言,比如java的原因,数据与事件是在类的同一级下,类似于
{
...data,
...methods
}
的形式,这种形式依赖命名或者说代码实施检测
如果前端这么使用,如果有重名,一定会依照顺序被覆盖
比如banckbone,用友的iuap
这里强调一下,因为data的监听,类似于rsjx,为开发人员主动监听,不存在结构转换的问题
结构如下
{
name:ko.observable(''),
getName(){
}
}
所以很容易出现
{
...data,
...methods
}
这种形式
冲突处理
我在把iuap改成vue写法的时候遇见过这个问题,假设真的重名了,怎么处理,应该是忽略,假设data中已存在并处理过改name,则在其他地方如methods时,不在处理,那么针对vue就是,假设props已经被监听,则忽略data,
vue源码
//初始化执行顺序
export function initState (vm: Component) {
vm._watchers = []
const opts = vm.$options
//依次进行初始化
if (opts.props) initProps(vm, opts.props)
if (opts.methods) initMethods(vm, opts.methods)
if (opts.data) {
initData(vm)
} else {
observe(vm._data = {}, true /* asRootData */)
}
if (opts.computed) initComputed(vm, opts.computed)
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}
//初始化data函数
function initData (vm: Component) {
//...
if{
//...
}else if (!isReserved(key)) {
//当属性并未被订阅过,即非props属性
proxy(vm, `_data`, key)
}
}
//...
}
ajax
异步js与xml,并不局限于数据,在jq中一直是可以用来获取后台json数据,xml结构,文本结构,jsonp甚至script,最早的组件加载器都是用ajax加载script实现的,比如easyui,ext自带的loader,一般会配合如下代码
loader('tableUI')
$(function(){
//1.业务代码
})
loader中会重新代理$(),已达到组件正确加载的目的
也包括百度,高德等第三方软件商提供的各种loader
amd与cmd
谈到async和ajax的时候大概是想描述这类代码
//同步的
async function main()=>{
//1.初始化代码
....
//2.初始化组件
//按需加载
//await $.getScript('xxx')
await loader('tableUI')
$.tableUi('#table');
}
//或者
//异步的
function main()=>{
//1.初始化代码
//2.初始化组件
loader('tableUI').then(()=>{
$.tableUi('#table');
})
}
requirejs与seajs的确有写法上的区别或者叫原理上的区别,问题是在具体实现时,尽量会要求同步为主,否则会有不必要的麻烦
比如金山云控制台,点击数据库【关系数据库】,再点击数据库【表格数据库】,业务层无发正常渲染,应该就是table组件没有家在上的原因
回流与重绘
在用java做dom解析的时候,遇见的一个问题,dom的属性可以修改,但节点类型无法修改,应该怎么处理
对应的其实就是回流与重绘
重绘
$0.align="right"
dom节点并没有修改,可以重复使用,使用后台也很好处理,Render树保持不变,相应的dom进行重绘即可
回流
对应的,如果将h2标签改成h3标签如何处理
无论是js还是java都需要通过函数处处理,一定是新增和删除dom,而新增与删除会修改Render树的坐标,故称为回流
针对java上,我可能会描述为
1.局部更新
调用实例对象的set系列
2.全局更新
调用静态函数changeDom系列
概念借用
图表系列,比如highchart或者某些图行处理上会引用类似的概念,比如只是修改了data,调用repaint,而修改了title或者整体的结构,那就是reflow
vm描述
其实用vm描述大概这样
export default {
data(){
return {
//监听dom树
domTree:[]
}
},
watch:{
renderTree(){
reflow();
}
},
computed:{
//获取render树
renderTree(){
return _.map(domTree,(dom)=>{
return {
dom:dom,
x:getX(dom),
y:getY(dom)
}
})
}
}
}
类似结构
而这个结构又类似于iview的table组件
使用iview的table组件不能进行编辑,要么重写一个EditTable,直接进行数据的绑定,但是会丢失很多配置性的功能,要么修改内部的rebuildData
我在写knockout下的table组件时,也遇见过类似的问题
抖动与节流
throttle/debounce这两个函数我也是今天才知道叫抖动和节流,我一般是放在高频处理上描述
原文如下
重复提交(高频处理)
- 重复提交
单位时间内,多次非幂等(post)请求,造成的后台重复录入现象
此处重点讨论前端的防重复提交的方式
- 快速点击
单位时间内,快速点击,触发异步事件,照成数据与ui混乱的现象
如导航,分页等,当导航快速点击,会使内容区异常抖动,且可能会与导航具体标签不一致
属于高频,但比较独特,包含了异步操作,故单独拿出来处理
- 高频事件
单位时间内,触发的各类事件,且响应速度低于请求,照成的卡顿或其他混乱现象,如快速点击,鼠标移动
以上三种情况之后统一描述重复提交
最终效果
- 阻止过多的ajax请求(post/get)
- 正确响应最后一次结果
关键技术点
- Aborted
xhr的abort函数
setTimeout
限制性UI
修改按钮/UI,弹菊花
重复判断
保存正在进行的ajax操作(0<readyState<4)及其url,当该操作存在,且又要发送相同请求时,视为重复提交
实现
实现的方式有很多,此处会尽量做到减少污染和依赖(变化)
根据影响范围,做以下区分
ajax拦截/包裹/封装
此处使用lodash的函数式写法,如果喜欢es7的装饰方式,需要做细微的调整
abort函数
对原有xhr(ajax),进行abort处理
对于post,并没有阻止ajax请求,请求已经发送,后台依然在处理请求
对于get,可以保证当前请求为正确的结果(解决快速点击)
实现
var global={};
$.ajaxSetup({
beforeSend:function(jqXHR, s ){
//为jqXHR注入url
var callbackContext = this;
jqXHR._url=callbackContext.url
}
});
function abort(ajax){
//获取上一个请求
var preajax = global[ajax._url];
preajax&&preajax.abort();
global[ajax._url]=ajax
};
var abort_ajax=_.flow($.get,abort);
abort_ajax('/');
abort_ajax('/');
abort_ajax('/');
abort_ajax('/');
此种实现默认url获取数据后,会对数据进行分发
分页,导航,搜索(度娘)均可使用
其他
若为传统(10年前-。-)的组件+url的形式,如
<select url="api/students"></select>
<select url="api/students"></select>
此时,会产生两条相同请求,获取数据后并不分发,需要改写代码
若对ajax进行了封装,使用注入Store/Model,vuex,flux之类的模型
根据不同的场景描述不同的action,性质是一样的
装饰
如果非要用es7装饰,注意,promise(现在)没有abort,以下写法,不好实现(单指写法,不探讨是否适合)
@abort
async submit(){
var data=await Store.load('');
console.log(data);
}
abort思路,可以解决异步交互(get)下,最后一次响应的问题
js是同步的,无法解决同步的多次提交(同步为卡顿,响应顺序并不会错误)
忽略(单例)
根据url,对ajax实现单例,即当前url正在通讯时,返回上一个ajax,不在创建信息ajax
实现
var ignore_ajax=function(params){
var preajax = global[params.url];
if(preajax){
return preajax
}
var ajax = $.ajax(params)
ajax.then(function(){
global[this.url]=undefined;
})
global[params.url] = ajax;
return ajax;
}
ignore_ajax({url:'/'});
ignore_ajax({url:'/'});
ignore_ajax({url:'/'});
ignore_ajax({url:'/'});
例子中省略global,ajaxSetup步奏,下同
一般由Store,Model等封装ajax的模块,配合readyState进行操作,即将ajax改成单例
无法保证最后一次请求的响应,故本身会配合ui进行限制性操作
保存系列,均可使用
其他
- 对于后台超级接口,需要个性化处理
- 会无视,结果未返回时,请求参数(body内)已变更的请求(配合ui)
throttle/debounce
高频(鼠标移动,窗口大小)请求中,调整请求和响应速度的方式,属于setTimeout的花样使用
实现
var debounce_ajax = _.debounce($.ajax,5000);
debounce_ajax('/')
其他
- throttle
优先执行,之后放哑炮,类似于忽略(单例),无法保证能够准确的响应最后一次操作
不依赖最后一次响应的事件,比如mousemove,对于异步需要配合ui限制(也可以改用忽略的方式),总之,不是最好的选择
- debounce
单位时间内收集数据,而后进行请求,
包含数据收集的输入框,都可以使用比如触发框,富文本自动保存
另:度娘的服务器就是屌
memoize
get幂等
实现
var memoize_ajax =_.memoize($.ajax)
其他
没错,就是缓存,主要用于数据更新频率低的幂等异步操作
比如requireJS,加载导航数据,菜单栏,甚至各种树结构
validate/error/timeout
禁止访问(权限),异常处理,超时等情况,虽然不属于重复提交,但处理上类似
限制ui
此处ajax依赖ui,属于ajax处理后台进阶表现,尤其是单例的情况下
当然,只用ui控制也无所谓,多入口保存的情况还是比较少的
disabled/active系列/转菊花
将整个操作函数封装为promise,并通过e获取相应ui
export function buttonDisable(promise){
//寻找依赖ui
var e=arguments[arguments.length-1]
var btn=e.target.parentElement;
//设置类 or 属性 or 转菊花
btn.setAttribute('disabled','')
promise()
.then(()=>{
btn.removeAttribute('disabled');
})
.catch(()=>{
btn.removeAttribute('disabled');
})
}
@buttonDisable
async submit(){
var data=await Post('/');
console.log(data);
}
es6写法(es5略)
整体包裹/限制的范围为submit的执行过程,若单独对ajax进行限制,对于嵌套/并行等各种花式ajax完全无爱
当然,如果强迫必须视图属性代替视图,也可以做如下操作
export function buttonDisable(vm){
return function(promise){
vm(false);
promise()
.then(()=>{
vm(true);
})
.catch(()=>{
vm(true);
})
}
}
@buttonDisable(vm.submitFlag)
async submit(){
var data=await Post('/');
console.log(data);
}
<button class="u-button u-button-info " data-bind="{click:submit,attr:{disabled:submitFlag}}">提交</button>
submitFlag,需要提前定义,转菊花监听submitFlag变化即可
封装ajax,监听readyState
async submit(){
var data=await Store.load('');
console.log(data);
}
<button class="u-button u-button-info " data-bind="{click:submit,attr:{disabled:Store.readyState}}">提交</button>
Store为ajax的封装,其中readyState为监听对象(属性),如果没有这一层...那就有一个,算一个-。-
其他
将整个执行函数处理为promise,而后进行个性化处理
最好不要在ajax处理中,包含ui操作
后台重复验证
提前提供id,将创建,改写为"修改",防止一对一的关系转换为一对多
ssh系列的token防重复提交,指的是该系列
缓存系列与数据库平级,不讨论
其他情况并不处理...
原文结束
我想说的应该是clearTimeout
线程
浏览器是多线程
html自身的至少三个
1.事件
比如setTimeout和dom1中的事件流
2.渲染/GUI
html/css
3.xhr
以及最后网景后来增加的
4.js引擎
以我个人的角度看,应该还有个主线程,去分配GUI与JS,顺便保证他们的互斥性,这个应该就是浏览器本身
我以前做java的时候,会说就当js与GUI在一个线程里即浏览器页面主线程,所以造成的GUI与js的互斥,这点的确是描述的有问题
而针对css与html包括js的并行,主要想描述的是依顺序运行,针对单核,多线程属于按顺序或按优先级,依次进行运算,这个属于强行解释
16ms
针对16ms,大概还有个14K,script压缩保持14K倍数的传说吧,tcp传输,每包起步14K
即某种意义上看1k与10k传输速度相同
300ms是测试的要求,大致是页面不能白屏超过300ms,否则按bug算
12-11补充
加载器
针对加载器(amd/cmd),除了开发过loader或者使用过外,针对UI组件,主要是ext与easyui,有过细节的优化
主要要针对的是不需要返回组件对象的组件,比如弹出层,模态框这些
属于在业务代码风格不变的情况下按需加载,类似于dll的动态引用
不需要返回结构的加载器
整体结构如下
//种子
$.modal = (config)=>{
if(!$.widgets.modal){
//加载模态框组件代码
loader('modal').then(()=>{
//生成模态框组件
$.widgets.modal(config);
})
}else{
$.widgets.modal(config);
}
}
其实更像是代理,如果其他需要返回的组件也这么处理,本身来讲与loader整体语法上是重复的,最主要是针对组件通讯或组件依赖完全无解,大致风格如下,
$('#xxx').tableUI({
})
.then(tableUI){
}
模态框加载
后续对模态框的加载又做了一次处理,这里类似业务代码的动态加载,现在基本上由路由统一处理,问题不大
模态框组将相当其他组件来讲,在交互上更依赖结构(data)而不是dom,尤其是在模态框由iframe或者window.open实现的时候
比如使用时
{
//业务使用
methods:{
async getUser(){
try{
var res = await UserModal(config);
cosnole.log('已选择',res)
}catch(e){
console.log('未选择')
}
}
}
}
模态框--UserModal,这里可以用继承,组合甚至装饰
import ModalComponent from './ModalComponent'
export default function Modal(config) {
return new Promise((resolve, reject) => {
const Instance = new Vue({
render(h) {
return h(ModalComponent)
},
methods: {
ok(data) {
resolve(data);
this.destroy();
},
remove() {
reject();
this.destroy();
},
destroy() {
document.body.removeChild(this.$el);
}
}
});
const component = Instance.$mount();
document.body.appendChild(component.$el);
})
}
最终业务实现
<template>
<Modal v-model="show" @on-ok="ok">
模态框
</Modal>
</template>
<script>
export default {
data(){
return {
show:true
}
},
methods:{
ok(){
this.$parent.ok({
name:'xx'
})
}
}
}
</script>
按需加载dom
这里其实是按需加载dom,把js再次Promse就是按需加载js与dom
应该说所有的串性交互的组件都可以这么实现(平行的也可以,或者依赖反馈,或者多次发送,比如导航模块和业务模块),以实现动态加载的目的,比如复杂的下拉或详情等