My Little World

learn and share


  • 首页

  • 分类

  • 标签

  • 归档

  • 关于
My Little World

数组的map和reduce

发表于 2018-12-22

数组的map方法

1
2
3
4
5
6
7
let arr = [1,2,3,4,5];
let arr1 = arr.map(function(val,index,arr){
console.log(val,index,arr)
return val+1
})
console.log(arr1)//[2, 3, 4, 5, 6]
console.log(arr) //[1, 2, 3, 4, 5]

在map中可以传递一个函数,数组的每一项作为参数调用这个函数,然后返回根据该数组项得到的结果,每一个数组项返会一个结果,从而组成新的数组

函数的参数有三个,第一项为数组按序传进的一个值,该值的index和数组本身

整个结果产生新数组,原来数组不变

数组的reduce 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
例1.
let arr = [1,2,3];
let result = arr.reduce(function(preresult,item){
return preresult += item;
},0)
console.log(result) //6

例2.
let result4 = arr.reduce(function(preresult,item){
preresult.name += item;
return preresult
},{name:0})
console.log(result4); //{name:6}

reduce 用于对数组每一项进行累计计算,每一项计算结果作为参数参加数组下一项的计算,最终返回计算结果

例1中单纯对数组每一项进行叠加处理,返回叠加结果
例2同样进行叠加只是叠加结果放在对象中返回

reduce(callback(),initval)

需要两个参数,一个是用于累计计算的callback函数,一个是初始值,见上两例

callback()函数有四个参数依次为:

preVal (上一次调用回调返回的值,或者是提供的初始值(initialValue))

currentValue (数组中当前被处理的元素)

index (当前元素在数组中的索引)

array (调用 reduce 的数组)

初始值会在第一次调用时当作回调函数callback的第一个参数使用

希望返回值是什么类型的数据,初始值就要设置为什么样的类型,callback返回值也是该类型;或者说是在这里设置返回值的类型

整个处理过程就是拿初始值initval和数组第一个值在callback里面进行处理,返回结果result,result当作callback的preval参数和数组第二个元素传入callback在处理,直到数组全部元素被处理完,返回一个最终处理结果

高级应用
对对象数组元素进行累计计算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
var reducers = {  
totalInEuros : function(state, item) {
return state.euros += item.price * 0.897424392;
},
totalInYen : function(state, item) {
return state.yens += item.price * 113.852;
}
};

var manageReducers = function(reducers) {
return function(state, item) {
return Object.keys(reducers).reduce(
function(nextState, key) {
reducers[key](state, item);
return state;
},
{}
);
}
};

var bigTotalPriceReducer = manageReducers(reducers);
var initialState = {euros:0, yens: 0};
var items = [{price: 10}, {price: 120}, {price: 1000}];
var totals = items.reduce(bigTotalPriceReducer, initialState);
console.log(totals);
//{euros: 1014.08956296, yens: 128652.76}

该例的目的是将对象数组items的元素属性price进行不同处理操作然后将结果分别保存到结果对象的属性中

处理的关键是manageReducers返回的作为items callback的函数bigTotalPriceReducer,

处理思路是items每用一个元素调用callback时,callback的处理过程是将这个元素作为参数调用reducers 对象里面设置的每一个处理函数,

调用reducers处理函数的过程同样使用一个reduce方法进行处理

Object.keys(obj)方法返回对象键值组成的数组

当items的元素参加完reducers所有处理函数后,返回的结果参加下一个元素的计算最终返回结果

扩展:

1.在reduces中设置对items不同属性的计算,从而得到不同属性的结算结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var reducers = {  
totalInEuros : function(state, item) {
return state.euros += item.p1;
},
totalInYen : function(state, item) {
return state.yens += item.price * 113.852;
}
};

var bigTotalPriceReducer = manageReducers(reducers);
var initialState = {euros:0, yens: 0};
var items = [{price: 10,p1:1}, {price: 120,p1:1}, {price: 1000,p1:1}];
var totals = items.reduce(bigTotalPriceReducer, initialState);
console.log(totals);
//{euros: 3, yens: 128652.76}

2.计算对象数组属性值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var manageReducers = function(state, item) {
return Object.keys(item).reduce(
function(nextState, key) {
state[key] +=item[key];
return state;
},
{}
);
};

var initialState = {price:0, p1: 0};
var items = [{price: 10,p1:1}, {price: 120,p1:1}, {price: 1000,p1:1}];
var totals = items.reduce(manageReducers, initialState);
console.log(totals);
//{price: 1130, p1: 3}

参阅资料

My Little World

js 知识点 一

发表于 2018-12-08

1.< script > 标签几个属性

async :立即异步下载,因此不保证能够按指定的先后顺序执行,但会在load事件前,DOMContentLoad前后执行
defer :延迟执行,也不保证按顺序执行,在DOMContentLoad前后执行
src :外部文件链接
charset :src指定代码的字符集,防止出现乱码
language :已废弃,表示脚本语言,
type :值为脚本语言内容类型(MIME),language替代属性,服务器传递过来类型为application/x-javascript需要注意,一个是可能导致脚本被忽略,另一个是可能触发浏览器XHTML标准模式;另外外链文件不是js文件时,一定要设置该值

2.js 代码执行过程

包括两个部分一个是编译,或者叫预处理过程,一个是执行
编译过程 首先会创建一个全局作用域,然后进行词法分析,
词法分析过程会涉及到变量提升和函数声明提前处理,
即词法分析到变量声明会先判断当前域到全局域有没有该变量声明,如果有,就会将该变量挂到该作用域的对象上,
如果到全局域都没有则以全局为背景,新建该变量,并为其分配内存,
对于基本类型,变量中会保存它的值,对于对象,变量中会保存值所在内存的内存地址,
在一定场景下,这里就相当于发生了变量提升,把变量从局部提升到了全局,或者将声明提前到了使用之前,
避免变量提升可以使用es6的let 关键字或者使用立即执行实现变量声明使用,但要注意使用let重复声明会报错,var 会同名覆盖;
当遇到函数声明时,会直接按声明顺序,将声明提前到代码顶端,
重复声明 的函数会被后者覆盖,
如果变量名与函数名相同,则无论原来词法位置如何,先将函数声明提前到顶端,不再进行变量声明,变量名在哪赋值,则原函数在哪被覆盖,
覆盖之前调用使用原函数
提前之后会为函数创建作用域,
这时创建的作用域就是函数的 【【scope】】属性,保存着指向父域到全局域的指针
(指针就是指向活动对象的一个地址,全局域浏览器环境就是windows,nodejs环境就是global)
将来执行时会根据此指针建立作用域链,作用域链就是指针列表,指向不同层级作用域的内存,在里面取值
以上就基本上是编译过程,除了一些特定的编译规则,编译过程还会会使用大量技巧进行优化提升编译速度
但在使用js时,需要注意try-catch,with和eval三个语法关键字的使用,因为在使用它们时,会创建块级作用域,
破坏掉编译器原有的创建管理作用域的规则,在编译时,编译器也就不会对其中的代码进行优化,最终导致性能变差

运行过程,基本就是在运行函数,函数在运行时会首先根据【【scope】】属性拿到父域到全局的指针,创建指针列表即作用域链,
然后创建函数执行的局部作用域,将函数中声明的变量挂到局部作用域活动对象上,然后将局部作用域指针推向作用域链前端,
在用到变量值时,会根据作用域链依次查找取值
函数执行结束后,局部活动对象被销毁,作用域链被销毁,进行垃圾回收
但是对于声明在一个函数内部的函数来说,这个声明的函数在声明时,就会包含其父域的活动对象,
因此在外部使用该函数时,可以访问到其父域的变量,实现了父域访问子域变量,延长了变量的使用范围,也就是常见的闭包现象,
缺点就是闭包的函数执行完毕后,因为本身【【scope】】会指向父域的活动对象,所以父域的活动对象始终不会被回收,除非闭包函数被销毁
从而有可能造成内存泄露,为什么说有可能,因为父活动对象占的内存其实是比较小的,造成内存泄露的真正原因是使用闭包易造成循环引用,
尤其是闭包作用域中保存dom结点时,如果是在IE中,dom和bom都是以C++的COM对象保存的,垃圾回收机制是技术策略,
因此循环引用的话,永远都不会被回收

补充,对于未以分号结尾的语句,会采用能合就合的原则,即,会尝试和下一行语句合并执行,如果能合并执行就合并执行,
不能合并执行就在中间加分号再执行,如果再执行不了就会报错
好处就是对于条件判断语句可进行一行一行判断
坏处就是如果下一行以大括号开头,上一行语句就会变成函数,导致出现意想不到的结果
两个例外,
对于return,break,continue语句不会做尝试合并;
对于++,–自增自减符后没有分号,上一行也没分号情况,自增自减会和下一行合并,上一行自行添加分号

以上即是我所了解的js运行机制

3.关于对象继承

3.0 new 操作符调用构造函数 执行过程
1.创建或者构造一个全新的对象
2.将构造函数的作用域赋给这个新的对象,
3.执行构造函数 (为这个新对象添加属性) ,第二,三步相当于func.call(newobj)
4.如果构造函数没有返回值或返回值非对象,则返回新对象,否则返回构造函数return的对象

3.1 继承的6种方式及优缺点
第一种原型链方式,子类的原型对象指向父类的实例
缺点
给原型添加方法必须要在替换原型的语句之后;
不能实现多继承(原型指向多个父对象);
所有属性共享
无法传递参数到父类进行初始化
第二种借助构造函数,在子类构造函数中使用call或者apply执行父类构造函数
优点
可以实现多继承
可以传递参数
方法属性不共享
缺点
只能继承构造函数中属性,不能继承原型上方法,不能复用方法
实例仅为子类实例,不是父类实例
第三种组合继承,以上两组结合在一起,两种方式都执行
缺点
要调用两次父类构造函数,影响性能
实例属性和原型属性各占一份,同名覆盖机制,重复占内存,没必要
第四种原型式继承,借助object()或者object.create()函数,缺点同原型链方式

1
2
3
4
5
function object(o){
function F(){}
F.prototype = o;
return new F();
}

第五种寄生式继承,利用原型式继承和增强对象过程的封装函数创建子类,缺点同原型链方式
第六种寄生组合式继承,保留借助构造函数部分,原型部分功能借助中间函数处理,
中间函数借助object拿到父类prototype,给到子类的prototype,从而完美解决组合继承的缺点

1
2
3
4
5
6
function inheritPrototype(subType,superType){
var prototype = object(superType.prototype)
prototype.constructor = subType
subType.prototype = prototype
}
subType.prototype.__proto__ = superType

3.2 es6 使用class extend实现继承
使用class声明构造函数时,本来挂在this上的属性,放在constructor()函数里面执行初始化,
挂在prototype上的方法直接放在class里面声明

使用extend继承时,需要在constructor里面先执行super函数才能使用this关键字,
增强方法同样直接在class中和constructor并列声明,相当于挂在prototype上

注意super函数只能在consructor中运行;super用作对象在constructor中使用时,相当于this,
添加属性会添加在当前对象上,运行函数时会调用继承的父类的方法

还有就是class extend继承null和 什么也不继承是,相当于继承Function.prototype
继承Object时,子类就相当于object 的复制,子类实例就是object实例

4.关于this 四种使用场景

普通函数执行,指向全局作用域
对象属性方法调用,指向调用对象
call,apply中强制绑定对象,this执行绑定的对象
构造函数中this指向构造的新对象
(箭头函数中this,指向词法作用域的父域)

5.js文件位置

放html底部原因:
下载解析执行过程是阻断式的,会停止页面渲染,造成白屏,影响用户体验;
另外JS中如果有对dom的操作,页面中还没有dom,获取不到,是不符合逻辑的,会报错

哪些js功能文件可以放顶部:
与css相关的js,比如rem单位的换算,需要根据根结点进行设置
使用的框架需要在根结点将浏览器支持的样式罗列出来

6.什么情况下用内部文件,内部文件比外部文件好处在哪?

内部文件相比外部文件最大好处就是性能提升,因为访问外部文件时,不管文件大小都会造成一次网络请求链接,请求服务器,下载文件,
解析文件,除了文件本身代码外,还要处理文件头文件尾的请求,增加了链接数,从而造成性能影响
对于功能短小精悍,不会被到处复用的js代码不适合采用外部文件,应该采用内部文件写法,尤其对手机端页面有性能提升

7.开发过程避免缓存方法

在浏览器开启禁用缓存的模式
手动清缓存 ctrl+shfit+del
引用的文件名添加随机数,浏览器根据文件名不同,就会重新获取资源更新缓存

8.使用严格模式弊端及解决办法

在代码压缩时,’use strict’ 这一行可能不在位于第一行,
或者后续代码不需要在严格模式下执行,被压缩在了严格模式范围内,导致了全部以严格模式执行

解决办法就是,将需要严格模式执行的代码放在匿名函数中,形成代码块,在函数中使用严格模式

9.立即执行函数好处

避免变量污染:有些变量仅在小功能内使用,将小功能封装起来,就可以避免这些变量暴露到全局,
另外函数本身没有函数名,不会增加全局变量
提升性能:功能所需变量全都在函数内时,查找变量快
有利于压缩:一部分代码执行需要用到的变量变量名太长,将这段代码封装成立即执行函数,将长变量名以参数形式传进去
避免全局命名冲突:一段代码需要用到两个代表不同功能但名称相同的变量,可以将该段代码封装成立即执行函数,
将其中一个变量以参数形式传递进来,达到换名的目的
保存闭包状态:循环执行异步代码时,将异步代码用立即执行函数包裹,函数内可保存本次循环的状态
改变代码运行顺序:umd,通用模块规范,function(fn){fn()}()

10.变量类型

根据当前变量的值的类型确定
如果想要进行标记,方法有三种
初始化时指定相应类型值
使用匈牙利标记法,用单字母表示类型,添加到变量命中,例sName 代表string类型
使用注释,变量声明时,在旁边添加注释

11.null vs undefined

转数字时,null –>0;undefined–>NaN
undefined可以当做windows对象上的一个属性,null不行

12. 类型转换

基本类型包括,undefined,null,boolean,string,number

基本类型 —> boolean Boolean(someval) 相当于 !!someval
undefined,null,NaN,’’,+0,-0 —> false
其他 —> true

基本类型 —> string String(someval)相当于 someval+’’
一般情况直接加引号
[] —> ‘’
{} —> ‘[object object]’

基本类型—>number + someval 相当于 Number(someval)
undefined,{} —> NaN
false, null,’’,[],—> 0
true —> 1
‘123’ —> 123
‘abc’ —> NaN
[]—> toString —> ‘’ —> 0
{} —> valueOf —> {} —> toString —> ‘[object object]’ —> NaN

基本类型 —>对象
undefined,null —> {}
true/123 —> {initval:true/123}
‘abc’ —> {initval:’abc’,length:3,0:’a’,1:’b’,2:’c’}

对象 —>基本类型

关于–对象分类:
内部对象(错误对象;常用对象:Boolean,Number,String,Object,Array,Function,Date,Exec;内置对象:Math,global,Json)
宿主对象:windows,document
自定义对象

关于–属性访问
obj.someprop—>检查obj是不是null/undefined—>是|不是—>报错|是不是Object,不是的话转object—>取值
obj[someval]—>检查obj是不是null/undefined—>是|不是—>报错|是不是Object,不是的话转object—>计算someval拿到string—>取值

Boolean —>true
Number —>基本类型转的|{}/{a:1}|Date—>相应值|NaN|时间戳
Array.toString:数组每一项转字符串
Function.toString:原代码字符串形式
Date.toString:日期和时间组合形式
Exec.toString:正则字符串形式

运算转换

Number()参数不能有非数字
parseInt()参数只能为字符串,非字符串会进行强制转化字符串,可含非数字,但不能以非数字开头
一元操作符对任何数据类型有效,得到数字

+二元运算侧重转字符串,然后进行字符串拼接
其中一个为字符串或者两个都是对象时,转字符串拼接
undefined,null,Boolean,数字混合运算+时,会被转数字再计算
{}为第一个运算值时,会被当做函数结束,不参与计算

1
2
3
4
5
6
7
8
9
10
11
12
null + null --> 0
undefined + null --> NaN
[] + [] --> ''
1 + [1,2] --> '11,2'
1 + {a:1} --> '1[object object]'
{a:1} + 1 --> 1
{} + {} --> NaN
({}) + {} --> '[object object][object object]'
[] + {} --> '[object object]'
{} + [] --> 0
var a = {}
a + a -->'[object object][object object]'

*/- 转数字,进行相减,如果不能转数字,返回NaN

>/< 侧重转数字
string < string 字典比较
非字符串<非字符串/字符串 都转数字然后比较

string == number 字符串转数字
boolean == 其他类型 二者转数字然后比较
null == undefined —>true undefined和null只与对方和自己==比较时为true,其他均为false
null === undefined —>false
非对象 == 对象 与数字比较object转数字,与字符串比较二者转数字再比较

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
'0' == null  //false
'0' == undefined //false
'0' == false //true
'0' == NaN //false
'0' == 0 //true
'0' == '' //true

false == null //false
false == undefined //false
false == NaN //false
false == 0 //true
false == '' //true
false == [] //true
false == {} //false

'' == null //false
'' == undefined //false
'' == NaN //false
'' == 0 //true
'' == [] //true
'' == {} //false

0 == null //false
0 == undefined //false
0 == NaN //false
0 == [] //true
0 == {} //false

[] == ![]

&&和|| 计算结果根据短路原则判断到了哪里,返回最后一个进行判断的值

运算符优先级
属性访问.
一元操作符(+,-,++,–)
二元操作符(+-*/)
比较(>,<)
相等(==,===,!=)
与&&,或||
三目运算符
赋值

运算符结合性
除了一元操作符,三目运算符和赋值是右结合,其他都是左结合

减少小数计算误差
尽量不使用小数进行比较或运算
转整数计算,再转回相应位数小数
使用toFixed()四舍五入
两个情境需要重复计算时,保证前后书写顺序

13. 数组相关方法

arrmethod

14.字符串相关方法

strmethod

15.日期相关方法

一共 33个
set/get(UTC)FullYear,Month,Date,Hours,Minutes,Seconds,Millseconds
set/get Time
get(UTC) Day
getTimezoneOffset

16. Math三个舍入方法

Math.ceil:比值大的最小整数
Math.round:四舍五入
Math.floor:比值小的最大整数

17. setInterval 注意问题

累积效应
代码执行时间大于间隔时间,后续调用会进行累积,累积会在短时间内连续触发

当用于动画时,因与显示器刷新频率不统一会造成视觉卡顿,解决办法如下:
一种是使用CSS3创建动画,根据显示器刷新执行动画
另一种是使用 requestAnimationframe(function(){})函数,也是在显示器刷新时执行
可以解决CSS3无法实现的,例如滚动控制等效果

18. 关于DOM

document 三个属性
url:地址栏中URL
domain:域名,跨域域名 X;父域名到子域名 X;
referer:上一页URL,可在跳转时,判断与当前页是否在同一域,是的话,就可以back,否则 location.href其他页面
dom.getAttribute(‘style’),得到样式字符串
dom.style 得到到样式对象
dom.getAttribute(‘onclick’) ,得到代码字符串
dom.onclick 得到函数对象

动态合集
使用时可能会造成死循环
使用框架如Jquery获取的合集不具有动态性
使用 queryselect(all)()获取到的也是静态合集

动态合集生成原理:
浏览器通过DOM树预先缓存起来,获取时,浏览器会通过缓存直接注册,创建一个变量返回

静态合集生成原理:
获取标识是一个CSS选择器,浏览器会先去解析字符串,判断是一个CSS选择器,
分析选择器并创建一个选择器结构,浏览器根据DOM树创建一个静态文件,即DOM树的一个快照
然后拿选择器和快照进行对比,符合选择器就放到合集中,知道整个快照对比完,再把合集的快照返回

19. 事件

绑定方式

直接通过HTML属性绑定
缺点
有可能响应函数还未被解析就被触发
事件名和回调1对1,修改繁琐
响应执行时,作用域为全局,不同浏览器解析规则不同,造成某些对象无法访问

使用dom属性绑定
冒泡阶段执行
缺点
只能绑定一个响应,后续赋值会被覆盖

IE attachEvent/detachEvent绑定/解绑
冒泡阶段执行
绑定多个时按按绑定顺序的逆序执行

addEventListener()/removeEventListener()绑定/解绑
第三个参数为对象时,passive属性为true时,会针对touchstart,touchend等特定事件通过开启两个线程,
一个执行浏览器默认行为,一个执行JS进行优化

事件优先级
浏览器在绑定事件而不是JS在绑定事件

1.html标签绑定事件会被dom属性事件覆盖
2.html标签优先执行,即使被属性事件覆盖,则执行被覆盖的属性事件
3.仅有监听事件和属性事件时,按绑定顺序执行,即事件对象的冒泡事件可能会在捕获事件之前执行

event对象属性
curentTarget:响应函数绑定的对象
target:发生事件的对象

My Little World

一些jQuery知识

发表于 2018-12-08

1.jquery对象与Element对象转换

1
2
3
4
5
6
7
//Element对象转jquery对象
var domObj = document.getElementById('id')
var $obj = $(domObj)//jQuery对象

//jquery对象转Element对象
var $box = $('.box')
var box = $box[0]

2.常用jQuery选择器接口
//传入对象 , 例$(document),把传入的对象包装成jQuery对象
$(this}

//传入函数 , 这个是在页面DOM文档加载完成后加载执行的,等效于在DOM加载完毕后执行了${document).ready()方 法。
$ (function (){})

//传入字符串 ,査询D0M节点包装成jQuery对象
$(.box)

//传入HTML字符串 创建DOM节点包装成jQuery对象
$(<div>)

//空
$()创建jQuery对象

3.如何把创建的Dom结点包装成jQuery对象
contetx.createElement创建DOM节点存储在数组中,调用merge方法把数组中存储的DOM节点的成员添加到jQuery实例对象上

4.jQuery实例对象length属性作用
存储DOM节点的数组对象平滑地添加到jQuery实例对象上

5.merge方法应用场景有哪些
合并数组
把数组成员合并在有length属性的对象上

6.$(document).ready()与$(function(){})的关系
$(document).ready()是对document.DOMContentLoaded事件的封装
$(function(){})每次调用$()传入的参数会收集在readyList数组中,
当document.DOMContentLoaded事件触发时依次执行readyList中收集的处理函数

My Little World

js 知识点 一

发表于 2018-12-08

1.< script > 标签几个属性

async :立即异步下载,因此不保证能够按指定的先后顺序执行,但会在load事件前,DOMContentLoad前后执行
defer :延迟执行,也不保证按顺序执行,在DOMContentLoad前后执行
src :外部文件链接
charset :src指定代码的字符集,防止出现乱码
language :已废弃,表示脚本语言,
type :值为脚本语言内容类型(MIME),language替代属性,服务器传递过来类型为application/x-javascript需要注意,一个是可能导致脚本被忽略,另一个是可能触发浏览器XHTML标准模式;另外外链文件不是js文件时,一定要设置该值

2.js 代码执行过程

包括两个部分一个是编译,或者叫预处理过程,一个是执行
编译过程 首先会创建一个全局作用域,然后进行词法分析,
词法分析过程会涉及到变量提升和函数声明提前处理,
即词法分析到变量时会先判断当前域到全局域有没有该变量声明,如果有,就会将该声明的变量挂到该作用域的对象上,
如果到全局域都没有则以全局为背景,新建该变量,并为其分配内存,
对于基本类型,变量中会保存它的值,对于对象,变量中会保存值所在内存的内存地址,
在一定场景下,这里就相当于发生了变量提升,把变量从局部提升到了全局,或者将声明提前到了使用之前,
避免变量提升可以使用es6的let 关键字或者使用立即执行实现变量声明使用,但要注意使用let重复声明会报错,var 会同名覆盖;
当遇到函数声明时,会直接按声明顺序,将声明提前到代码顶端,
重复声明 的函数会被后者覆盖,
如果变量名与函数名相同,则无论原来词法位置如何,先将函数声明提前到顶端,不再进行变量声明,变量名在哪赋值,则原函数在哪被覆盖,
覆盖之前调用使用原函数
提前之后会为函数创建作用域,
这时创建的作用域就是函数的 【【scope】】属性,保存着指向父域到全局域的指针
(指针就是指向活动对象的一个地址,全局域浏览器环境就是windows,nodejs环境就是global)
将来执行时会根据此指针建立作用域链,作用域链就是指针列表,指向不同层级作用域的内存,在里面取值
以上就基本上是编译过程,除了一些特定的编译规则,编译过程还会会使用大量技巧进行优化提升编译速度
但在使用js时,需要注意try-catch,with和eval三个语法关键字的使用,因为在使用它们时,会创建块级作用域,
破坏掉编译器原有的创建管理作用域的规则,在编译时,编译器也就不会对其中的代码进行优化,最终导致性能变差

运行过程,基本就是在运行函数,函数在运行时会首先根据【【scope】】属性拿到父域到全局的指针,创建指针列表即作用域链,
然后创建函数执行的局部作用域,将函数中声明的变量挂到局部作用域活动对象上,然后将局部作用域指针推向作用域链前端,
在用到变量值时,会根据作用域链依次查找取值
函数执行结束后,局部活动对象被销毁,作用域链被销毁,进行垃圾回收
但是对于声明在一个函数内部的函数来说,这个声明的函数在声明时,就会包含其父域的活动对象,
因此在外部使用该函数时,可以访问到其父域的变量,实现了父域访问子域变量,延长了变量的使用范围,也就是常见的闭包现象,
缺点就是闭包的函数执行完毕后,因为本身【【scope】】会指向父域的活动对象,所以父域的活动对象始终不会被回收,除非闭包函数被销毁
从而有可能造成内存泄露,为什么说有可能,因为父活动对象占的内存其实是比较小的,造成内存泄露的真正原因是使用闭包易造成循环引用,
尤其是闭包作用域中保存dom结点时,如果是在IE中,dom和bom都是以C++的COM对象保存的,垃圾回收机制是计数策略,
因此循环引用的话,永远都不会被回收

补充,对于未以分号结尾的语句,会采用能合就合的原则,即,会尝试和下一行语句合并执行,如果能合并执行就合并执行,
不能合并执行就在中间加分号再执行,如果再执行不了就会报错
好处就是对于条件判断语句可进行一行一行判断
坏处就是如果下一行以大括号开头,上一行语句就会变成函数,导致出现意想不到的结果
两个例外,
对于return,break,continue语句不会做尝试合并;
对于++,–自增自减符后没有分号,上一行也没分号情况,自增自减会和下一行合并,上一行自行添加分号

以上即是我所了解的js运行机制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
//补充1
每个值都有内存地址,变量名指向内存地址/保存内存地址
变量名携带内存地址存储在栈中
保存变量值的内存空间在堆中(数据类型:object,function,函数缓存区,函数加载区),相同模样的值,内存地址不同,(即不同变量名,赋值为看似一样的对象时,会不相等;更改变量的值,即更改指向的地址)
常量值保存在池中(数据类型:string,number,boolean),相同值,内存地址相同

变量是个引用,常量是个值
== 比较值
=== 比较地址

//补充2
function func(a,b,c){
/*
* 逻辑代码执行前
* step1 创建AO对象
* AO ={}
* step2 初始化AO对象
* AO = {
* this:undefined,//内置
* arguments:undefined,//默认
* a:undefined,//参数
* b:undefined,//参数
* c:undefined,//参数
* inner:undefined//函数内声明
* }
* step3 赋值
* AO = {
* this:window,//内置
* arguments:[length:2,0:1,1:2],//默认
* a:1,//参数
* b:2,//参数
* c:undefined,//参数
* inner:undefined//仅声明还未执行
* }
* step4 处理函数声明,有相同变量名的函数覆盖赋值已有变量名,新函数名直接挂到AO上
* AO = {
* this:window,//内置
* arguments:[length:2,0:1,1:2],//默认
* a:function a(){},//有同名函数声明进行覆盖
* b:2,//参数
* c:undefined,//参数
* inner:undefined,//仅声明还未执行
* innerFunc:function(){}//添加函数声明
* }
*/
console.log(arguments);
console.log(global)
console.log(inner)
////////////////
var inner = 20;//执行到该步,inner被赋值为20
function inner(){

}
function innerFunc(){

}
console.log(inner)
}

var global=20
func(1,2)

3.关于对象继承

3.0 new 操作符调用构造函数 执行过程
1.创建或者构造一个全新的对象
2.将构造函数的作用域赋给这个新的对象,
3.执行构造函数 (为这个新对象添加属性) ,第二,三步相当于func.call(newobj)
4.如果构造函数没有返回值或返回值非对象,则返回新对象,否则返回构造函数return的对象

3.1 继承的6种方式及优缺点
第一种原型链方式,子类的原型对象指向父类的实例
缺点
给原型添加方法必须要在替换原型的语句之后;
不能实现多继承(原型指向多个父对象);
所有属性共享
无法传递参数到父类进行初始化
第二种借助构造函数,在子类构造函数中使用call或者apply执行父类构造函数
优点
可以实现多继承
可以传递参数
属性不共享
缺点
只能继承构造函数中属性,不能继承原型上方法,不能复用方法
实例仅为子类实例,不是父类实例
第三种组合继承,以上两组结合在一起,两种方式都执行
缺点
要调用两次父类构造函数,影响性能
实例属性和原型属性各占一份,同名覆盖机制,重复占内存,没必要
第四种原型式继承,借助object()或者object.create()函数,缺点同原型链方式

1
2
3
4
5
function object(o){
function F(){}
F.prototype = o;
return new F();
}

第五种寄生式继承,利用原型式继承和增强对象过程的封装函数创建子类,缺点同原型链方式
第六种寄生组合式继承,保留借助构造函数部分,原型部分功能借助中间函数处理,
中间函数借助object拿到父类prototype,给到子类的prototype,从而完美解决组合继承的缺点

1
2
3
4
5
6
function inheritPrototype(subType,superType){
var prototype = object(superType.prototype)
prototype.constructor = subType
subType.prototype = prototype
}
subType.prototype.__proto__ = superType

3.2 es6 使用class extend实现继承
使用class声明构造函数时,本来挂在this上的属性,放在constructor()函数里面执行初始化,
挂在prototype上的方法直接放在class里面声明

使用extend继承时,需要在constructor里面先执行super函数才能使用this关键字,
增强方法同样直接在class中和constructor并列声明,相当于挂在prototype上

注意super函数只能在consructor中运行;super用作对象在constructor中使用时,相当于this,
添加属性会添加在当前对象上,运行函数时会调用继承的父类的方法

还有就是class extend继承null和 什么也不继承是,相当于继承Function.prototype
继承Object时,子类就相当于object 的复制,子类实例就是object实例

4.关于this 四种使用场景

普通函数执行,指向全局作用域
对象属性方法调用,指向调用对象
call,apply中强制绑定对象,this执行绑定的对象
构造函数中this指向构造的新对象
(箭头函数中this,指向词法作用域的父域)

5.js文件位置

放html底部原因:
下载解析执行过程是阻断式的,会停止页面渲染,造成白屏,影响用户体验;
另外JS中如果有对dom的操作,页面中还没有dom,获取不到,是不符合逻辑的,会报错

哪些js功能文件可以放顶部:
与css相关的js,比如rem单位的换算,需要根据根结点进行设置
使用的框架需要在根结点将浏览器支持的样式罗列出来

6.什么情况下用内部文件,内部文件比外部文件好处在哪?

内部文件相比外部文件最大好处就是性能提升,因为访问外部文件时,不管文件大小都会造成一次网络请求链接,请求服务器,下载文件,
解析文件,除了文件本身代码外,还要处理文件头文件尾的请求,增加了链接数,从而造成性能影响
对于功能短小精悍,不会被到处复用的js代码不适合采用外部文件,应该采用内部文件写法,尤其对手机端页面有性能提升

7.开发过程避免缓存方法

在浏览器开启禁用缓存的模式
手动清缓存 ctrl+shfit+del
引用的文件名添加随机数,浏览器根据文件名不同,就会重新获取资源更新缓存

8.使用严格模式弊端及解决办法

在代码压缩时,’use strict’ 这一行可能不在位于第一行,
或者后续代码不需要在严格模式下执行,被压缩在了严格模式范围内,导致了全部以严格模式执行

解决办法就是,将需要严格模式执行的代码放在匿名函数中,形成代码块,在函数中使用严格模式

9.立即执行函数好处

避免变量污染:有些变量仅在小功能内使用,将小功能封装起来,就可以避免这些变量暴露到全局,
另外函数本身没有函数名,不会增加全局变量
提升性能:功能所需变量全都在函数内时,查找变量快
有利于压缩:一部分代码执行需要用到的变量变量名太长,将这段代码封装成立即执行函数,将长变量名以参数形式传进去
避免全局命名冲突:一段代码需要用到两个代表不同功能但名称相同的变量,可以将该段代码封装成立即执行函数,
将其中一个变量以参数形式传递进来,达到换名的目的
保存闭包状态:循环执行异步代码时,将异步代码用立即执行函数包裹,函数内可保存本次循环的状态
改变代码运行顺序:umd,通用模块规范,function(fn){fn()}()

10.变量类型

根据当前变量的值的类型确定
如果想要进行标记,方法有三种
初始化时指定相应类型值
使用匈牙利标记法,用单字母表示类型,添加到变量命中,例sName 代表string类型
使用注释,变量声明时,在旁边添加注释

11.null vs undefined

转数字时,null –>0;undefined–>NaN
undefined可以当做windows对象上的一个属性,null不行

12. 类型转换

基本类型包括,undefined,null,boolean,string,number

基本类型 —> boolean Boolean(someval) 相当于 !!someval
undefined,null,NaN,’’,+0,-0 —> false
其他 —> true

基本类型 —> string String(someval)相当于 someval+’’
一般情况直接加引号
[] —> ‘’
{} —> ‘[object object]’

基本类型—>number + someval 相当于 Number(someval)
undefined,{} —> NaN
false, null,’’,[],—> 0
true —> 1
‘123’ —> 123
‘abc’ —> NaN
[]—> toString —> ‘’ —> 0
{} —> valueOf —> {} —> toString —> ‘[object object]’ —> NaN

基本类型 —>对象
undefined,null —> {}
true/123 —> {initval:true/123}
‘abc’ —> {initval:’abc’,length:3,0:’a’,1:’b’,2:’c’}

对象 —>基本类型

关于–对象分类:
内部对象(错误对象;常用对象:Boolean,Number,String,Object,Array,Function,Date,Exec;内置对象:Math,global,Json)
宿主对象:windows,document
自定义对象

关于–属性访问
obj.someprop—>检查obj是不是null/undefined—>是|不是—>报错|是不是Object,不是的话转object—>取值
obj[someval]—>检查obj是不是null/undefined—>是|不是—>报错|是不是Object,不是的话转object—>计算someval拿到string—>取值

Boolean —>true
Number —>基本类型转的|{}/{a:1}|Date—>相应值|NaN|时间戳
Array.toString:数组每一项转字符串
Function.toString:原代码字符串形式
Date.toString:日期和时间组合形式
Exec.toString:正则字符串形式

运算转换

Number()参数不能有非数字
parseInt()参数只能为字符串,非字符串会进行强制转化字符串,可含非数字,但不能以非数字开头
一元操作符对任何数据类型有效,得到数字

+二元运算侧重转字符串,然后进行字符串拼接
其中一个为字符串或者两个都是对象时,转字符串拼接
undefined,null,Boolean,数字混合运算+时,会被转数字再计算
{}为第一个运算值时,会被当做函数结束,不参与计算

1
2
3
4
5
6
7
8
9
10
11
12
null + null --> 0
undefined + null --> NaN
[] + [] --> ''
1 + [1,2] --> '11,2'
1 + {a:1} --> '1[object object]'
{a:1} + 1 --> 1
{} + {} --> NaN
({}) + {} --> '[object object][object object]'
[] + {} --> '[object object]'
{} + [] --> 0
var a = {}
a + a -->'[object object][object object]'

*/- 转数字,进行相减,如果不能转数字,返回NaN

>/< 侧重转数字
string < string 字典比较
非字符串<非字符串/字符串 都转数字然后比较

string == number 字符串转数字
boolean == 其他类型 二者转数字然后比较
null == undefined —>true undefined和null只与对方和自己==比较时为true,其他均为false
null === undefined —>false
非对象 == 对象 与数字比较object转数字,与字符串比较二者转数字再比较

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
'0' == null  //false
'0' == undefined //false
'0' == false //true
'0' == NaN //false
'0' == 0 //true
'0' == '' //false

false == null //false
false == undefined //false
false == NaN //false
false == 0 //true
false == '' //true
false == [] //true
false == {} //false

'' == null //false
'' == undefined //false
'' == NaN //false
'' == 0 //true
'' == [] //true
'' == {} //false

0 == null //false
0 == undefined //false
0 == NaN //false
0 == [] //true
0 == {} //false

[] == ![] //true

&&和|| 计算结果根据短路原则判断到了哪里,返回最后一个进行判断的值

运算符优先级
属性访问.
一元操作符(+,-,++,–)
二元操作符(+-*/)
比较(>,<)
相等(==,===,!=)
与&&,或||
三目运算符
赋值

运算符结合性
除了一元操作符,三目运算符和赋值是右结合,其他都是左结合

减少小数计算误差
尽量不使用小数进行比较或运算
转整数计算,再转回相应位数小数
使用toFixed()四舍五入
两个情境需要重复计算时,保证前后书写顺序

13. 数组相关方法

arrmethod

1
2
let arr = []
let iterator = arr[Symbol.iterator]() //可以获取数组迭代器

遍历对象/数组的方法
for
while(do…while)
forEach
map
reduce
for…of
for…in
iterator
generator

14.字符串相关方法

strmethod

15.日期相关方法

一共 33个
set/get(UTC)FullYear,Month,Date,Hours,Minutes,Seconds,Millseconds
set/get Time
get(UTC) Day
getTimezoneOffset

16. Math三个舍入方法

Math.ceil:比值大的最小整数
Math.round:四舍五入
Math.floor:比值小的最大整数

17. setInterval 注意问题

累积效应
代码执行时间大于间隔时间,后续调用会进行累积,累积会在短时间内连续触发

当用于动画时,因与显示器刷新频率不统一会造成视觉卡顿,解决办法如下:
一种是使用CSS3创建动画,根据显示器刷新执行动画
另一种是使用 requestAnimationframe(function(){})函数,也是在显示器刷新时执行
可以解决CSS3无法实现的,例如滚动控制等效果

18. 关于DOM

document 三个属性
url:地址栏中URL
domain:域名,跨域域名 X;父域名到子域名 X;
referer:上一页URL,可在跳转时,判断与当前页是否在同一域,是的话,就可以back,否则 location.href其他页面
dom.getAttribute(‘style’),得到样式字符串
dom.style 得到到样式对象
dom.getAttribute(‘onclick’) ,得到代码字符串
dom.onclick 得到函数对象

动态合集
使用时可能会造成死循环
使用框架如Jquery获取的合集不具有动态性
使用 queryselect(all)()获取到的也是静态合集

动态合集生成原理:
浏览器通过DOM树预先缓存起来,获取时,浏览器会通过缓存直接注册,创建一个变量返回

静态合集生成原理:
获取标识是一个CSS选择器,浏览器会先去解析字符串,判断是一个CSS选择器,
分析选择器并创建一个选择器结构,浏览器根据DOM树创建一个静态文件,即DOM树的一个快照
然后拿选择器和快照进行对比,符合选择器就放到合集中,知道整个快照对比完,再把合集的快照返回

19. 事件

绑定方式

直接通过HTML属性绑定
缺点
有可能响应函数还未被解析就被触发
事件名和回调1对1,修改繁琐
响应执行时,作用域为全局,不同浏览器解析规则不同,造成某些对象无法访问

使用dom属性绑定
冒泡阶段执行
缺点
只能绑定一个响应,后续赋值会被覆盖

IE attachEvent/detachEvent绑定/解绑
冒泡阶段执行
绑定多个时按按绑定顺序的逆序执行

addEventListener()/removeEventListener()绑定/解绑
第三个参数为对象时,passive属性为true时,会针对touchstart,touchend等特定事件通过开启两个线程,
一个执行浏览器默认行为,一个执行JS进行优化

事件优先级
浏览器在绑定事件而不是JS在绑定事件

1.html标签绑定事件会被dom属性事件覆盖
2.html标签优先执行,即使被属性事件覆盖,则执行被覆盖的属性事件
3.仅有监听事件和属性事件时,按绑定顺序执行,即事件对象的冒泡事件可能会在捕获事件之前执行

event对象属性
curentTarget:响应函数绑定的对象
target:发生事件的对象

My Little World

拖拽

发表于 2018-12-04

使用 React 实现拖放的技术要点

1.如何使用 React 的鼠标事件系统
2.如何判断拖放开始和拖放结束
3.如何实现拖放元素的位置移动 (可分为两种,一种是直接拖着具体要被移动的dom移动;另外一种是具体dom留在原位,拖着具体dom的影子移动,确定位置后,再将具体的dom放过去)
4.拖放状态在组件中如何维护

鼠标移动可能会超出要移动的组件和他的父组件,除了在document上监听,还可以在一个全局透明遮罩层上监听MouseMove和MouseUp好处:拖放过程不会选中其他任何元素,防止点击到其他组件

其他情景思考:
每个条目高度不一致,如何确定移动位置?
条目所在的列表有折叠,存在滚动条,如何根据滚动条确定位置?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100

import React, { Component } from "react";

require("./DndSample.css");

const list = [];
for (let i = 0; i < 10; i++) {
list.push(`Item ${i + 1}`);
}

const move = (arr, startIndex, toIndex) => {
arr = arr.slice();
arr.splice(toIndex, 0, arr.splice(startIndex, 1)[0]);
return arr;
};

const lineHeight = 42;
class DndSample extends Component {
constructor(props) {
super(props);
this.state.list = list;
}
state = {
dragging: false,
draggingIndex: -1,
startPageY: 0,
offsetPageY: 0,
};

handleMounseDown = (evt, index) => {
this.setState({
dragging: true,
startPageY: evt.pageY,
currentPageY: evt.pageY,
draggingIndex: index,
});
};
handleMouseUp = () => {
this.setState({ dragging: false, startPageY: 0, draggingIndex: -1 });
};
//如果往下滑,就一次把下一条数据交换位置,如果往上移动,就一次把上一条数据交换位置,
handleMouseMove = evt => {
let offset = evt.pageY - this.state.startPageY;
const draggingIndex = this.state.draggingIndex;
if (offset > lineHeight && draggingIndex < this.state.list.length - 1) {
// move down
offset -= lineHeight;
this.setState({
list: move(this.state.list, draggingIndex, draggingIndex + 1),
draggingIndex: draggingIndex + 1,
startPageY: this.state.startPageY + lineHeight,
});
} else if (offset < -lineHeight && draggingIndex > 0) {
// move up
offset += lineHeight;
this.setState({
list: move(this.state.list, draggingIndex, draggingIndex - 1),
draggingIndex: draggingIndex - 1,
startPageY: this.state.startPageY - lineHeight,
});
}
this.setState({ offsetPageY: offset });
};

getDraggingStyle(index) {
if (index !== this.state.draggingIndex) return {};
return {
backgroundColor: "#eee",
transform: `translate(10px, ${this.state.offsetPageY}px)`,
opacity: 0.5,
};
}

render() {
return (
<div className="dnd-sample">
<ul>
{this.state.list.map((text, i) => (
<li
key={text}
onMouseDown={evt => this.handleMounseDown(evt, i)}
style={this.getDraggingStyle(i)}
>
{text}
</li>
))}
</ul>
{this.state.dragging && ( //在一个遮罩层上监听MouseMove和MouseUp
<div
className="dnd-sample-mask"
onMouseMove={this.handleMouseMove}
onMouseUp={this.handleMouseUp}
/>
)}
</div>
);
}
}

export default DndSample;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
.dnd-sample ul {
display: inline-block;
margin: 0;
padding: 0;
background-color: #eee;
}

.dnd-sample li {
cursor: default;
list-style: none;
border-bottom: 1px solid #ddd;
padding: 10px;
margin: 0;
width: 300px;
background-color: #fff;
}

.dnd-sample-mask {
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.1);
}
My Little World

对话框

发表于 2018-12-04

使用React Portals

React 16.3 新引入的 API
可以将虚拟 DOM 映射到任何真实 DOM 节点
解决了漂浮层的问题,比如Dialog,Tooltip 等

主要思路使用ReactDOM.createPortal将自定义dialog利用id挂到根标签上,dialog样式(包括悬浮)自己需要通过css定义,弹窗的显示和隐藏通过state操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
mport React from "react";
import ReactDOM from "react-dom";
import { Button } from "antd";
import "./PortalSample.css";

export default class PortalSample extends React.PureComponent {
state = { visible: false };
renderButton() {
return (
<Button type="primary" onClick={() => this.setState({ visible: true })}>
打开对话框
</Button>
);
}
renderDialog() {
return (
<div className="portal-sample">
<div>这是一个对话框!</div>
<br />
<Button
type="primary"
onClick={() => this.setState({ visible: false })}
>
关闭对话框
</Button>
</div>
);
}
render() {
if (!this.state.visible) return this.renderButton();
return ReactDOM.createPortal(
this.renderDialog(),
document.getElementById("dialog-container"),//在APP组件中定义
);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
.portal-sample {
position: absolute;
padding: 20px;
width: 500px;
height: 300px;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
background-color: #fff;
border-radius: 10px;
border: 1px solid #ddd;
box-shadow: 0px 0px 20px 2px #ddd;
}

使用UI组件库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import React from "react";
import ReactDOM from "react-dom";
import { Button, Modal } from "antd";
import "./PortalSample.css";

export default class PortalSample extends React.PureComponent {
state = { visible: false };
renderButton() {
return (
<Button type="primary" onClick={() => this.setState({ visible: true })}>
打开对话框
</Button>
);
}
renderDialog() {
return (
<Modal
visible={this.state.visible}
onCancel={() => this.setState({ visible: false })}
>
<div>这是一个对话框!</div>
</Modal>
);
}
render() {
return (
<div>
{this.renderButton()}
{this.renderDialog()}
</div>
);
}
}
My Little World

布局

发表于 2018-12-04

实现布局的几种方式

1.从0开始用 CSS实现
2.使用 CSS Grid 系统 (通过class实现,无需关心布局如何实现,同时可以适应不同屏幕的尺寸)
3.使用组件库,例如 antd(通过组件标签实现)

布局常见场景:侧边栏宽度可调整
1.手动实现拖放逻辑
2.使用 local storage 存储宽度位置

上中下结构,中间高度随父级高度自适应,头部和底部高度固定

1
2
3
4
5
<div className="app-layout1">
<div className="header">Header</div>
<div className="content">content</div>
<div className="footer">Footer</div>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<style>
.app-layout1 {
width: 500px;
height: 400px;
position: relative;
background-color: #eee;
text-align: center;
}

.app-layout1 .header {
line-height: 60px;
border-bottom: 2px solid #fff;
}
.app-layout1 .content {
position: absolute;
bottom: 60px;
top: 60px;
left: 0;
right: 0;
}
.app-layout1 .footer {
border-top: 2px solid #fff;
line-height: 60px;
bottom: 0;
left: 0;
right: 0;
position: absolute;
}
</style>

导航布局,左边是导航栏,右边上中下结构

1
2
3
4
5
6
<div className="app-layout2">
<div className="header">Header</div>
<div className="sider">Sider</div>
<div className="content">Content</div>
<div className="footer">Footer</div>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
.app-layout2 {
width: 500px;
height: 400px;
position: relative;
background-color: #eee;
text-align: center;
padding-left: 150px;
line-height: 60px;
}

.app-layout2 .header {
border-bottom: 2px solid #fff;
}
.app-layout2 .content {
position: absolute;
bottom: 60px;
top: 60px;
left: 150px;
right: 0;
}
.app-layout2 .sider {
width: 150px;
position: absolute;
border-right: 2px solid #fff;
top: 0;
left: 0;
bottom: 0;
}
.app-layout2 .footer {
border-top: 2px solid #fff;
bottom: 0;
left: 150px;
right: 0;
position: absolute;
}

导航栏随鼠标滑动调整宽度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import React from "react";
import { Button } from "antd";
import "./LayoutResize.css";

export default class LayoutResize extends React.PureComponent {
state = {
dragging: false,
startPageX: 0,
siderWidth: 150,
};

handleMouseDown = evt => {
this.setState({
dragging: true,
startPageX: evt.pageX,
});
};
handleMouseUp = () => {
this.setState({
dragging: false,
});
};
handleMouseMove = evt => {
let siderWidth = this.state.siderWidth + evt.pageX - this.state.startPageX;
if (siderWidth < 20 || siderWidth > 300) return;
this.setState({
siderWidth,
startPageX: evt.pageX,
});
};
render() {
const { dragging, siderWidth } = this.state;
const pxWidth = `${siderWidth}px`;
return (
<div className="app-layout-resize" style={{ paddingLeft: pxWidth }}>
<div className="header">Header</div>
<div className="sider" style={{ width: pxWidth }}>
Sider
</div>
<div className="content" style={{ left: pxWidth }}>
Content
</div>
<div className="footer" style={{ left: pxWidth }}>
Footer
</div>
<div
className="sider-resizer"
style={{ left: pxWidth }}
onMouseDown={this.handleMouseDown}
/>
{dragging && (
<div
className="resize-mask"
onMouseMove={this.handleMouseMove}
onMouseUp={this.handleMouseUp}
/>
)}
</div>
);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
.app-layout-resize {
width: 500px;
height: 400px;
position: relative;
background-color: #eee;
text-align: center;
padding-left: 150px;
line-height: 60px;
}

.app-layout-resize .header {
border-bottom: 2px solid #fff;
}
.app-layout-resize .content {
position: absolute;
bottom: 60px;
top: 60px;
left: 0;
right: 0;
}
.app-layout-resize .sider {
width: 150px;
position: absolute;
border-right: 2px solid #fff;
top: 0;
left: 0;
bottom: 0;
}
.app-layout-resize .footer {
border-top: 2px solid #fff;
bottom: 0;
left: 150px;
right: 0;
position: absolute;
}

.app-layout-resize .sider-resizer {
position: absolute;
left: 148px;
width: 6px;
top: 0;
bottom: 0;
cursor: col-resize;
}
.app-layout-resize .resize-mask {
background: rgba(0, 0, 0, 0);
position: fixed;
left: 0;
top: 0;
right: 0;
bottom: 0;
cursor: col-resize;
}
My Little World

表单 & 列表

发表于 2018-12-04

表单功能点

  1. 初始数据,提交和跳转

表单各项配置的优化,可以通过对UI框架的API进行封装成高阶组件,然后在使用时,通过配置高阶组件的属性,将需要的配置项传递给UI框架API,减少代码冗余,配置清晰

提交后状态通过中间件返回promise判断接下来操作

2.
错误处理(可依靠UI框架进行判断)
动态表单元素(在传入高阶组件属性时采用函数返回值形式动态加载表单元素,在函数中处理要不要显示表单元素)
内容动态加载(在componentDidMount中进行数据请求,以及初始状态处理)

列表页功能点

搜索,数据缓存和分页

开发列表也要考虑的技术要点
1.如何翻页 (数据来自服务器还是客户端缓存)
2.如何进行内容搜索 (当前页面数据搜索还是服务器端数据搜索)
3.如何缓存数据
4.何时进行页面刷新

列表页数据通过store拿数据渲染,翻页,查找,刷新通过触发action然后从服务器拿到数据后,更新store,从而组件重新渲染

store 模型
listItems:当前页id集合数组[id1,id2]
byId:{id1:{id:id1,name:xxx},id2:{id:id2,name:xxx}}
keyword:关键字string
page:number 页数
fetchListPending:bool是否正在加载数据
FetchListError:OBJECT数据加载出错
listNeedReload:bool是否需要重新加载(做判断条件,例如编辑之后,设为true,在列表页拿到判断为true则重新获取列表数据)

URL设计
将页数和关键字当做路由参数传递在componentDidupdated中获取参数判断是否重新获取数据

缓存更新,加载状态,错误处理

通过store模型中的相关数据进行判断展示

页面数据需要来源多个请求的处理

页面数据来自多个请求
1.请求之间无依赖关系,可以并发进行
2.请求有依赖,需要依次进行 (promise)
3.请求完成之前,页面显示 Loading 状态
loading数据状态由当前数据推导确定

内容页的加载和缓存

内容页和列表页的数据关系
1.简单业务:列表页数据包含内容页的数据 (注意页面刷新情况数据的获取)
2.复杂业务:内容页数据需要额外获取 (每次进来都重新获取)
3.内容页数据的缓存(将数据放在store中,在store中拿数据)

基于React Router 实现分步操作

向导页面需要考虑的技术要点
1.使用 URL进行导航的好处 (可以直接刷新其中一个步骤)

  1. 表单内容存放的位置
  2. 页面状态如何切换

将表单元素放在一个统一的form中,通过路由判断第几步,进而显示不同的表单元素,需要注意的是,切换下一步时,当前组件消失,配置的数据也会消失,需要将当前所填数据进行保存,以便在返回上一步时有数据

集成第三方 JS 库的技术要点

1.使用 ref 获取原生 DOM 节点引用
2.手动将组件状态更新到 DOM 节点
(对于数据驱动UI展示的情况,第一次渲染和之后更新需要手动操作DOM的过程,将数据注入过程隔离出来,单独处理成一个函数,再当react 部分操作导致需要重新渲染时,再调用,将react状态映射到第三方DOM中)
3.组件销毁时移除原生节点 DOM 事件

基于路由实现菜单导航

1.菜单导航只是改变 URL 状态
2.根据当前 URL 显示菜单的当前状态(使用navLink标签实现,注意react-router和redux一起使用时,url发生变化,组建要重新render时,必须要让组件绑定到router的store上,即让react-router也一起渲染)

如何避免应用出现性能问题

1.了解常见的性能问题场景 (键盘输入,鼠标移动是否卡顿)
2.时刻注意代码的潜在性能问题 (何时拆分组件,有优化空间;能否高效更新,组件拆分是否够细,足够细的话,组件越接近纯函数,就越可能减少参与到diff操作的可能性,从而提高渲染速度)
3.注重可重构的代码 (代码耦合性低,几乎不依赖外界,也不被外界依赖,对于有重构可能性的代码保留重构空间,当其他优先级较高的性能问题解决后,再进行代码重构)
4.了解如何使用工具定位性能问题

网络性能优化:自动化按需加载

1.什么是按需加载 (切换页面时才加载相应页面,而不是一开始就将所有页面加载进来)
2.使用 Webpack 的 import API
3.使用 react-loadable 库实现 React 异步加载
利用分包加载,在配置路由时实现,在总路由中删除,防止打包到总包中

1
2
3
4
5
import loadable from 'react-loadable'
const listPage = loadable({
loader:()=>import ('./listPage'),
loading:()=><div>loading...</div>
})

listPage页面在加载该页时,会自动加载listPage的独立打包文件

使用 reselect 避免重复计算
reselect 可以创建自动缓存的数据处理流程,通过保存计算结果,避免重复计算

异步渲染的两个部分

时间分片
DOM操作的优先级低于浏览器原生行为,例如键盘和鼠标输入,从而保证操作的流畅。
(setstate导致的重新render等页面行为(滚动,输入)结束后再执行)
1.虚拟 DOM 的 diff 操作可以分片进行 (对操作进行序列化,然后进行合并或者优先级处理)
2.React 新 API: unstatble_deferredUpdates(低级别的setstate)
3.Chrome 新 API:requestIdleCallback(浏览器级别的api,告诉代码内存空闲(不再滚动或敲击键盘),可以进行一些之前优先级低的UI更新操作,时间分片基础)

渲染挂起
虚拟DOM节点可以等待某个异步操作的完成,并指定timeout后,才完成真正的渲染
(可以不再注意什么时候显示loading状态,取消loading状态,虚拟dom节点可以自己等待异步操作的完成,只要虚拟dom节点返回一个promise,渲染引擎就会等promise结束后再去render)

  1. 新内置组件:Timeout

  2. unstatble_deferUpdate

借助工具发现性能问题

1.使用 React DevTool 找到多余渲染
chrome 插件,可查看react应用的状态,帮助找到不必要的render,从而进行调优—> [highlight update]
2.使用 Chrome DevTool 定位性能瓶颈
什么样的组件花了多长时间做了什么样的渲染—>performance

My Little World

Rekit

发表于 2018-12-04

背景
一个独立功能通常需要多个文件组成 (组件,reducer,action,路由等文件)
代码模板很复杂 (reducer,action需要代码模板,实际上填写的逻辑只是一小部分)
重构极为困难
(需要改动的地方很多)
项目复杂后很难理解和维护(无法直观的看到他们之间的关系)

思路

Rekit:更好的代码导航

  1. 语义化的组织源代码文件
  2. 使用子 Tab来展示项目元素的各个部分
  3. 直观的显示和导航某个功能的所有依赖

Rekit: 一键生成项目元素(它是一个IDE)

  1. 直观的 UI 用于生成组件,action,reducer 等
  2. 模板代码遵循最佳实践(只需填入具体逻辑)
  3. 支持命令行方式创建项目元素

Rekit: 重构非常容易

  1. 右键菜单重命名或者删除某个项目元素
  2. 所有相关代码都会一次性重构从而保证一致性
  3. 详细的 log信息显示重构的完整修改

Rekit: 可视化的项目架构

  1. 项目总体架构的可视化图表
  2. 项目依赖关系的图表

Rekit 是如何工作的?

  1. 定义了基于feature 的可扩展文件夹结构(文件夹结构有一定规则,可以进行解析)
  2. 基于最佳实践生成代码和管理项目元素
  3. 提供工具和 IDE 确保代码和文件夹结构遵循最佳实践
    (减少工作量)

遵循最佳实践

1.以 feature 方式组织代码 (功能如果是必须的就放在已有模块底层实现,如果是原有功能增强的实现,则新拆分成功能模块)
2.拆分组件,action 和 reducer
3.拆分路由配置

通过代码自动生成保持一致性
1.文件夹结构一致性
2.文件名一致性
3.变量名一致性
4.代码逻辑的一致性

使用 React Router 管理路由授权

1.实现基础:React Router 的动态路由机制 (
不采用各个页面判断是否登录再redirect的方式跳转,路由配置(添加属性,那些可以在未登录下访问,或者哪些在未登录下不能访问)时就去动态监测当前用户是否登录,如果登录则全部路由生效;反之某些路由配置就不生效===>不生效处理,在解析router JSON数据时根据store中登录状态和路由配置的属性判断,未登录则跳转403,其子路由清空
)
2.区分受保护路由和公开路由
3.访问未授权路由时重定向到登录页面

My Little World

Redux

发表于 2018-12-04

为什么需要redux

原有组件间通信方式错综复杂,逐层传递,不明了,方便组件间通信
用store管理所有组件的状态

redux 特性

single source of truth:状态来源唯一;

可预测性:state +action = new state

纯函数更新Store:输出取决于输入,内部不会依赖任何除参数以外外部元素产生副作用

store 三个函数

getState()

dispatch(action)

subscribe(listener)

action 描述行为的对象集合

reducer 更新state的具体行为

几个工具函数

combineReducers 把多个reducer 合成一个新的reducer,并定义每个reducer在store中的对应节点

bindActionCreators 封装actionCreator和store.dispatch调用过程

1
2
3
4
5
6
7
8
9
10
function actionCreator(){
return {
type:'addItem',
text:'......'
}
}
store.dispatch(actionCreator())
===>
actionCreator = bindActionCreators(actionCreator,store.dispatch)
actionCreator()

connect 创建高阶组件,对store 特定属性进行监听和绑定,从而在这些属性变化时自动刷新组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
import React from "react";
import { bindActionCreators, createStore } from "redux";
import { Provider, connect } from "react-redux";

// Store initial state
const initialState = { count: 0 };

// reducer
const counter = (state = initialState, action) => {
switch (action.type) {
case "PLUS_ONE":
return { count: state.count + 1 };
case "MINUS_ONE":
return { count: state.count - 1 };
case "CUSTOM_COUNT":
return { count: state.count + action.payload.count };
default:
break;
}
return state;
};

// Create store
const store = createStore(counter);

// Action creator
function plusOne() {
// action
return { type: "PLUS_ONE" };
}

function minusOne() {
return { type: "MINUS_ONE" };
}

export class Counter extends React.Component {
render() {
const { count, plusOne, minusOne } = this.props;//这三者通过connect关联store后获得
return (
<div className="counter">
<button onClick={minusOne}>-</button>
<span style={{ display: "inline-block", margin: "0 10px" }}>
{count}
</span>
<button onClick={plusOne}>+</button>
</div>
);
}
}
//组件需要数据,state是store.getState()获得的数据集合根节点
//性能问题:如果将store中的整个state绑定到组件上,那state上任何一个属性发生变化都会引起组件更新,所以向组件传递数据时,将数据绑定到尽可能的最小范围,实现只有绑定的数据变化时才引起组件更新
function mapStateToProps(state) {
return {
count: state.count
};
}
//组件中需要用到的触发函数
function mapDispatchToProps(dispatch) {
return bindActionCreators({ plusOne, minusOne }, dispatch);
}
//使用connect将redux和react 组件结合起来,形成高阶组件
const ConnectedCounter = connect(mapStateToProps, mapDispatchToProps)(Counter);

export default class CounterSample extends React.Component {
render() {
return (
<Provider store={store}> //根节点定义Provider,挂上store,其所有子节点均可以访问到
<ConnectedCounter />
</Provider>
);
}
}

主要流程

1.定义redux store中需要的东西:reducer,actionCreator
2.使用createStore 根据reducer创建store
3.定义组件,确定需要哪些数据和触发函数
4.根据组件需要,创建数据获取函数,封装触发函数相应actionCreator
5.使用connect结合redux和组件形成高阶组件在Provider 中使用

异步Action

异步 action不是特殊action而是多个同步 action的组合使用

一个AJAX的Action发出后,到达中间件,进行预处理,根据预处理的结果dispatch不同的action出去,给reducer,然后更新store

异步Action不是redux的一个概念而是action的一种设计模式

不同的action还是同步的action,只是异步action把这些同步的action放在了不同阶段去dispath

中间件
1.截获某种特定类型的action,进行特殊处理
2.发出action

如何组织action和reducer

‘标准’形式redux action的问题
1.所有Action放一个文件,会无限扩展
2.Action,Reducer分开,实现业务逻辑时需要来回切换
3.系统中有哪些Action不够直观
新的方式
单个action和reducer放在同一个文件,一个文件一个action
然后将所有action导入同一文件,所有reducer导入同一文件
1.易于开发:不用在action和reducer文件间来回切换
2.易于维护:每个action文件都很小,容易理解
3.易于测试:每个业务逻辑只需要对应一个测试文件
4.易于理解:文件名就是action名字,文件列表就是action列表
a sample
问题:拆分这么细的话,文件会多很多,对包的大小也会有影响

答:webpack 确实会为每个文件生成一小段固定的元代码,同样的代码行数,文件越多 bundle 确实会越大。但这个并不会成为性能瓶颈,两个原因:1. 拆分后代码数量不会显著增多,增加的元代码部分相比整个包的大小基本可以忽略;2. 生产环境普遍会启用 gzip,对于重复的元代码信息会被有效压缩,对比拆分前,gzip 后的包大小基本没有差异

不可变数据

redux在修改state时采用新对象替换老对象的思路,不在原来对象上做修改,在新对象中复制老对象,包含要修改的部分,通过对比新老对象的引用不同,引起节点更新—-redux运行基础

为何需要不可变数据
1.性能优化(通过对比新旧state不是同一个引用即可判断store发生了变化,从而不用进行深层遍历,比较具体的值,redux中的store都是不可变数据,每个节点都是不可变数据,当一个组件绑定在一个节点上,只需判断前后状态的引用是否相等,从而判断store是否发生变化,进而决定是否要更新组件)
2.易于调试和跟踪(store变化时可以看到前后状态,便于调试)
3.易于推测(任何时刻可以知道是什么引起store发生变化,根据action前后的状态判断,action是否被进行了正确处理)

如何操作不可变数据
1.原生写法{…},Object.assign() 性能最好

1
2
3
4
5
6
const state = {
filter:'completed',
todos:['learn react']
}
const newState1 = {...state,todos:[...state.todos,'learn redux']}
const newState = Object.assign({},state,{todos:[...state.todos,'learn redux']})

2.immutability-helper 适合需要深层次节点处理,需要引入类库,熟悉相应语法

1
2
3
4
5
6
import update from 'immutability-helper'
const state = {
filter:'completed',
todos:['learn react']
}
const newState = update(state,{todos:{$push:['learn redux']})

3.immer 可以像操作原对象一样生成新对象,性能最差(当节点层数多,结构复杂时,需要为每个属性建立代理),适合应用程序小,场景简单的情况

1
2
3
4
5
6
7
8
import produce from 'immer'
const state = {
filter:'completed',
todos:['learn react']
}
const newState = produce(state,draftState =>{
draftState.todos.push('learn redux')
})

1…101112…25
YooHannah

YooHannah

246 日志
1 分类
21 标签
RSS
© 2025 YooHannah
由 Hexo 强力驱动
主题 - NexT.Pisces