js基础知识系列(三)

DOM 中监听节点变化的事件(高级程序设计第 13.4.6 节)

DOM2 级的変动事件是为 XML 或 html 的 DOM 设计的,不特定于某种语言。

变动事件的分类有 7 种

  1. DOMSubtreeModified:在 DOM 结构中发生任何变化时触发
  2. DOMNodeInserted:在一个节点作为子节点被插入到另一个节点中时触发
  3. DOMNodeRemoved:在节点从其父节点中被移除时触发
  4. DOMNodeInsertedIntoDocument:在一个节点被直接插入文档中或者通过子树间接插入文档后触发。在 DOMNodeInserted 之后触发
  5. DOMNodeRemovedFromDocument:在一个节点被直接从文档中删除或通过子树间接从文档中移除之前触发。在 DOMNodeRemoved 之后触发
  6. DOMAttrModified:在特性被修改之后触发
  7. DOMCharacterDataModified:在文本节点的值发生变化的时候触发

常用的浏览器支持的有 3 种

  1. DOMSubtreeModified:在 DOM 结构中发生任何变化时触发
  2. DOMNodeInserted:在一个节点作为子节点被插入到另一个节点中时触发
  3. DOMNodeRemoved:在节点从其父节点中被移除时触发

应用

页面中嵌套有iframe且需要父页面自适应iframe高度时可以通过监听iframeDOM变化来实现

React学习系列(二)

高阶组件

高阶组件其实是一个函数,接收一个组件作为参数,返回一个包装组件作为返回值,类似于高阶函数。

属性代理

操作props

可以对原组件的props进行增删改查,通常是查找和增加,删除和修改的话,需要考虑到不能破坏原组件。

通过refs访问组件实例

可以通过ref回调函数的形式来访问传入组件的实例,进而调用组件相关方法或其他操作。

提取state

通过传入 props 和回调函数把 state 提取出来

包裹WrappedComponent

为了封装样式、布局等目的,可以将WrappedComponent用组件或元素包裹起来。

反向继承

高阶组件继承于被包裹的React组件

1
2
3
4
5
6
7
const MyContainer = (WrappedComponent)=>{
class extends WrappedComponent {
render(){
return super.render();
}
}
}

渲染劫持

渲染劫持就是指的是高阶组件可以控制 WrappedComponent的渲染过程,并渲染各种各样的结果。我们可以在这个过程中在任何React元素输出的结果中读取、增加、修改、删除props,或读取或修改React元素树,或条件显示。又或者用样式包裹元素树

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
function hoc(ComponentClass) {
return class HOC extends ComponentClass {
render() {
const elementTree = super.render();
elementTree.props.children = elementTree.props.children.filter((z) => {
return z.type !== "ul" && z;
}
const newTree = React.cloneElement(elementTree);
return newTree;
}
}
}

@hoc
export default class ComponentClass extends React.Component {
render() {
const divStyle = {
width: '100px',
height: '100px',
backgroundColor: 'red'
};

return (
<div>
<p style={{color: 'brown'}}>啦啦啦</p>
<ul>
<li>1</li>
<li>2</li>
</ul>
<h1>哈哈哈</h1>
</div>
)
}
}

操作state

HOC可以读取,编辑和删除WrappedComponent实例的state,可以添加state。不过这个可能会破坏WrappedComponent的state,所以,要限制HOC读取或添加state,添加的state应该放在单独的命名空间里,而不是和WrappedComponent的state混在一起。

条件渲染

当 this.props.loggedIn 为 true 时,这个 HOC 会完全渲染 WrappedComponent 的渲染结果。(假设 HOC 接收到了 loggedIn 这个 prop)

1
2
3
4
5
6
7
8
9
10
11
function iHOC(WrappedComponent) {
return class Enhancer extends WrappedComponent {
render() {
if (this.props.loggedIn) {
return super.render()
} else {
return null
}
}
}
}

解决WrappedComponent名字丢失问题

用HOC包裹的组件会丢失原先的名字,影响开发和调试。可以通过在WrappedComponent的名字上加一些前缀来作为HOC的名字,以方便调试。

1
2
3
4
5
6
7
8
9
10
11
class HOC extends ... {
static displayName = `HOC(${getDisplayName(WrappedComponent)})`
//
}

// getDisplayName
function getDisplayName(WrappedComponent) {
return WrappedComponent.displayName ||
WrappedComponent.name ||
'Component'
}

React学习系列(一)

react16以前的diff算法

虚拟dom树是一个tree结构,每一个DOM节点是一个对象,存储对应的虚拟DOM信息,更新时通过树的遍历实现DOM树的更新。

tree比较

通过遍历更新前后树的结构进行比较,若更新前后相同深度的节点结构不同,则删掉更新前的节点并停止当前节点的更深层次比较

component比较

react假设不同的component具有不同的结构,若component不同则删除原节点并停止更深层次的比较,然后创建新的节点。

数组元素比较

数组元素默认通过key进行比较,若不存在key则采用index进行比较,为提高性能建议给数组元素key赋值

  • 插入:直接插入
  • 删除:直接删除
  • 移动:当旧列表中的元素所在位置小于在新列表中元素位置时才会移动元素位置,且把上一次在旧列表中查找到元素位置设置为下一次查找的起始位置

待做

  • react16之后的版本把虚拟DOM改为链表结构,使用Fiber进行比较,其比较算法实现

js基础知识系列(二)

变量、作用域和内存问题

  • 参数传递按值传递

    参数是引用数据类型时,传入数据是引用数据类型的地址

    1
    2
    3
    4
    5
    6
    7
    8
    function setName(obj) {
    obj.name = "test1";
    obj = {};
    obj.name = "test2";
    }
    var test = {};
    setName(test);
    console.log(test.name); // test1
  • 垃圾收集

    • 标记清除:进入环境标记,离开环境清除标记
    • 引用计数:跟踪每个值引用的次数,引用次数为 0 时,回收所占用内存,当相互引用时,由于引用计数永远无法为 0 而导致内存永远无法释放造成内存泄漏
    1
    2
    3
    4
    5
    6
    function test() {
    var tesObj1 = {}; // tesObj1 引用加1 = 1
    var tesObj2 = {}; // tesObj2 引用加1 = 1
    tesObj1.test = tesObj2; // tesObj2 引用加1 = 2
    tesObj2.test = tesObj1; // tesObj1 引用加1 = 2
    } // tesObj1 引用减1 = 1, tesObj2 引用减1 = 1,tesObj1和tesObj2引用都不为0内存不释放
    • IE 中的BOMDOMC++COM模型的实现,使用引用计数,在相互引用时请使用手动断开相互应用的方式释放内存
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    function test() {
    var ele = document.getElementById("root"); // tesObj1 引用加1 = 1
    var tesObj2 = {}; // tesObj2 引用加1 = 1
    ele.test = tesObj2; // tesObj2 引用加1 = 2
    tesObj2.test = ele; // ele 引用加1 = 2

    // 其它操作
    ele.test = null; // tesObj2 引用减1 = 1
    tesObj2.test = null; // ele 引用减1 = 1
    } // ele 引用减1 = 0, tesObj2 引用减1 = 0,ele和tesObj2引用都为0内存回收
  • 管理内存

    • 局部变量会在离开执行环境时自动解除引用
    • 全局变量需要手动解除引用以释放占用内存
      1
      2
      3
      4
      5
      6
      7
      function createObj() {
      var temp = new Object();
      return temp;
      }
      var test = createObjt(); // 全局变量
      // 其它操作
      test = null// 解除引用,释放内存

引用类型

  • Oject类型
  • Array类型
    • 数组初始化,默认值为undefined
      1
      2
      var arr1 = [2, 3]; // 不推荐,IE8之前三个元素:1、2、undefined,其它为两个元素
      var arr2 = [, , ,]; // 不推荐,IE8之前4个元素都为undefined,其它为3个元素都为undefined
    • 数组长度,最大元素加 1,length可读写
    • 检测数组
      • 一个执行环境可以使用instancof,多个框架中传递数组且各自框架中的构造函数不同时无法检测
      • ES5使用Array.isArray()判断是否为数组,IE9+及其它
    • 转化方法,默认使用元素加,的方式拼接为字符串,可以使用join的方式拼接元素
    • 栈方法:pushpop
    • 队列方法:pushshift,通过unshiftpop模拟反向队列
    • 排序:sortreverse(反转数组)
    • 操作方法
      • concat:连接两个数组产生新数组
      • slice:基于当前数组创建新数组
      • splice:向数组中插入项
    • 位置方法:indexOflastIndexOf
    • 迭代方法:everyfilterforEachsomemap
    • 归并方法:reducereduceRight,参数function(prev, curr, index, array){return valur;}

js基础知识系列(一)

script标签

可以通过内联和外联的方式引入脚本,当引入外部脚本后,当前标签内联的脚本就不会执行

属性

  • async:立即下载脚本,但不阻塞页面渲染,脚本下载完毕之后立即执行,不能保证脚本按照顺序执行
  • charset:指定脚本的编码字符集
  • defer:脚本延迟到页面完全解析后依次执行脚本
  • language:脚本语言,已废弃
  • src:脚本url
  • type:脚本语言类型(也称为MIME类型),外联脚本可不填,可以为text/ecmascripttext/javascript,默认为text/javascript

javascript基本语法

标识符

  • 必须以字母、下划线、或美元符号开头
  • 其它字符可以为字母、下划线、美元符号或数字等字符

严格模式(ECMAScrpit5引入)

  • 通过"use strict;"启用严格模式
  • 本质是一个编译指示,告诉引擎切换到严格模式
  • 严格模式下,ECMAScrpit3一些不确定行为会抛出异常

数据类型

基本的数据类型(简单数据类型)有 5 种:undefinednullbooleannumberstring,复杂数据类型object

  • typeof操作符

    • null或者对象都返回object
  • Boolean类型转化

数据类型 转化为true的值 转化为false的值
string 任何非空值 “”
number 任何非 0 数值(包括无穷大) 0 和 NaN
object 任何对象 null
undefined undefined
  • number类型

    • 八进制用0开头,16 进制用0x开头
    • 科学计数法3e5表示 300000
    • 数值转化
      • true转化为 1,false转化为 0
      • undefined转化为NaN
  • string类型

    • toString方法,参数可选基数 2、8、10、16,默认为 10 进制
    • String方法
      • 若存在toString方法,则调用toString方法转化为 10 进制字符串
      • 若为null,则返回"null"
      • 若为undefined,则返回"undefined"
  • object对象

    • constructor:创建对象的函数
    • hasOwnProperty:属性是否存在于当前对象而不是原型链中
    • isPropertyOf:检查传入对象是否为当前对象的原型
    • propertyIsEnumerable:检查属性是否可以用for-in枚举
    • toLocaleString:返回对象的字符串表示
    • toString:返回对象的字符串表示
    • valueOf:返回对象的字符串、数值或布尔值表示,通常于toString相同

操作符

  • 位操作符

    • ~按位非
    • &按位与
    • |按位或
    • ^按位异或
    • >>有符号右移
    • <<有符号左移
    • >>>无符号右移
  • 相等操作符

    • 相等(==)和不相等(!=),强制转化数据类型后再比较
    • 全等(===)和全不等(!==),不转化数据类型直接比较
  • 逗号操作符

    • 一条语句执行多个操作
    • 赋值时返回表达式的最后一项

语句

  • for - in语句循环,没有顺序,nullundefined会抛出异常

  • label语句,在代码中添加标签,和breakcontinue配合使用

    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
    // 存在lable标签
    let count = 0;
    for (let i = 0; i < 10; i++) {
    outermost: for (let j = 0; j < 10; j++) {
    for (let k = 0; k < 10; k++) {
    if (i >= 5 && k >= 5) {
    break outermost;
    }
    count++;
    }
    }
    }
    console.log(count); // 525

    // 不存在lable标签
    let count = 0;
    for (let i = 0; i < 10; i++) {
    for (let j = 0; j < 10; j++) {
    for (let k = 0; k < 10; k++) {
    if (i >= 5 && k >= 5) {
    break;
    }
    count++;
    }
    }
    }
    console.log(count); // 750
  • with语句

    • 限定作用域在局部对象中,若在局部变量中找不到定义,则在with对象中查找对应的属性

      1
      2
      3
      4
      with (location) {
      console.log(pathname); // location.pathname
      console.log(this); // window
      }
    • with造成性能下降及调试困难,建议少用

  • 函数

    • arguments参数

      • 类似数组,可通过下标形式访问参数,通过 length 属性获取传入参数个数

      • arguments值与对应参数值同步,但不是共用一块内存,而是值的复制

        1
        2
        3
        4
        5
        function doAdd(num1, num2) {
        arguments[1] = 10;
        return arguments[0] + num2;
        }
        console.log(doAdd(1, 2)); // 11
    • 没有签名,因为参数是由包含 0 个或者多个值的数组实现的

    • 没有重载,后定义的函数覆盖之前定义的函数

日志记录和日志分割

pm2介绍

生产环境的node管理工具

pm2日志

  • pm2默认输出日志路径

    1
    2
    错误日志:  /home/username/.pm2/logs/
    普通日志: /home/username/.pm2/logs/
  • 存在问题

    • 无法对日志文件进行切割
    • 清理日志文件需要手动触发

pm2-logrotate日志管理

安装pm2-logrotate

pm2 install pm2-logrotate

pm2-logrotate参数配置

  • max_size (Defaults to 10M): 单个日志文件的最大值,例如:10G, 10M, 10K

  • retain (Defaults to 30 file logs): 可以存储的最大日志文件数量, 例如为7时,表示可以保存7个最新的日志文件加当前使用的日志文件

  • compress (Defaults to false): 压缩存储的历史日志文件,不包括当前使用的日志文件

  • dateFormat (Defaults to YYYY-MM-DD_HH-mm-ss) : 日志文件名称格式

  • rotateModule (Defaults to true) : Rotate the log of pm2’s module like other apps

  • workerInterval (Defaults to 30 in secs) : 调用检查日志文件大小任务的定时器时间间隔,默认值为30秒,最小值为1秒

  • rotateInterval (Defaults to 0 0 * * * everyday at midnight): This cron is used to a force rotate when executed. We are using node-schedule to schedule cron, so all valid cron for node-schedule is valid cron for this option. Cron style :

  • TZ (Defaults to system time): This is the standard tz database timezone used to offset the log file saved. For instance, a value of Etc/GMT-1, with an hourly log, will save a file at hour 14 GMT with hour 13 GMT-1 in the log name.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    *    *    *    *    *    *
    ┬ ┬ ┬ ┬ ┬ ┬
    │ │ │ │ │ |
    │ │ │ │ │ └ day of week (0 - 7) (0 or 7 is Sun)
    │ │ │ │ └───── month (1 - 12)
    │ │ │ └────────── day of month (1 - 31)
    │ │ └─────────────── hour (0 - 23)
    │ └──────────────────── minute (0 - 59)
    └───────────────────────── second (0 - 59, OPTIONAL)

参考文档

react+webpack项目中的babel配置

babel配置位置

  • 项目根目录.babelrc中配置,json格式

    1
    2
    3
    4
    {
    "presets": ["预设条件"],
    "plugins": ["插件"],
    }
  • package.json文件中配置

    1
    2
    3
    4
    5
    6
    7
    {
    "name": "project",
    "version": "0.0.1",
    "babel": {
    // babel配置
    }
    }

react中的babel配置

  • react解析jsx语法,npm install babel-preset-react安装相关依赖

  • es2015解析es6语法,npm install babel-preset-es2015安装相关依赖

  • es提案各个阶段的语法支持插件,npm install babel-preset-stage-0 babel-preset-stage-1 babel-preset-stage-2 babel-preset-stage-3安装相关依赖

    1
    2
    3
    4
    {
    "presets": ["react", "es2015"],
    "plugins": ["插件"],
    }

配置示例

  • 无参数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    {
    "presets": [
    "react",
    "es2015"
    ],
    "plugins": [
    "transform-es2015-arrow-functions", //转译箭头函数
    "transform-es2015-classes", //转译class语法
    "transform-es2015-spread", //转译数组解构
    "transform-es2015-for-of" //转译for-of
    ]
    }
  • 有参数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    {
    "presets": [
    "react",
    "es2015"
    ],
    "plugins": [
    //改为数组,第二个元素为配置项
    ["transform-es2015-arrow-functions", { "spec": true }]
    ]
    }

说明

参考资源

js学习(三)

DOM和事件对象

  • 事件冒泡:事件由目标元素逐渐向上传递到document
  • 事件捕获:事件有document逐渐向下传递逐渐追溯到目标元素

Event事件对象的通用属性

名称 说明 返回值
type 事件名称 字符串(如mouseover)
target 事件指向的目标元素 HTMLElement
currentTarget 带有当前被触发事件监听器的元素 HTMLElement
eventPhase 事件生命周期阶段 数值
bubbles 如果事件可以在文档中冒泡返回true,否则返回false boolean
cancelable 如果事件有可以撤销的默认行为返回true,否则返回false boolean
timeStamp 事件创建的时间,时间不可用就返回0 字符串
stopPropagation() 在当前事件监听器触发后,阻止其在元素中的流动,包括冒泡和捕获 void
stopImmediatePropagation() 立即终止事件在在元素中的流动,当前元素上未被触发的事件监听器会被忽略 void
preventDefault() 阻止浏览器执行与事件关联的默认操作 void
defaultPrevented 如果调用了preventDefault()返回true boolean
eventPhase值
名称 说明
Event.CAPTURING_PHASE 捕获阶段
Event.AT_TARGET 目标阶段
Event.BUBBLING_PHASE 冒泡阶段

Document对象事件

名称 说明
readystatechange 在readystate属性发生变化时触发

window事件对象

名称 说明
onabort 在文档或者资源加载过程被终止时触发
onbeforeprint 在用户调用window.print()方法,但尚未给用户提供打印时触发
onafterprint 在用户完成打印之后触发
onerror 在文档或者资源加载发生错误时触发
onhashchange 在锚部分发生变化时触发
onload 在文档或者资源加载完成时触发
onpopstate 触发后提供一个关联浏览器的状态对象
onresize 在窗口缩放时触发
onunload 在文档从或者浏览器中卸载时触发

鼠标事件

名称 说明
click 点击鼠标并释放鼠标键时触发
dblclick 在两次点击并释放鼠标键时触发
mousedown 在鼠标左键按下时触发
mouseenter 在光标移入元素或者其后代元素区域时触发
mouseleave 在光标移出元素及所有后代元素区域时触发
mousemove 在光标在元素上移动时触发
mouseout 指针移出元素,或者移到它的子元素上
mouserover 指针移到有事件监听的元素或者它的子元素内
mouseup 释放鼠标时触发
事件特有属性
名称 说明 返回值
button 点击的键,0左(主)键,1中键,2右(次)键 数值
altkey 是否按下alt/option键 boolean
clientX 相对于目标元素的x轴坐标 number
clientY 相对于目标元素的y轴坐标 number
screenX 相对于窗口的x轴坐标 number
screenY 相对于窗口的y轴坐标 number
shiftkey 是否按下shift键 boolean
ctrlkey 是否按下ctrl键 boolean

键盘焦点事件

名称 说明
blur 失去焦点
focus 获得焦点
focusin 即将获得焦点
focusout 即将失去焦点

键盘事件

名称 说明
keydown 按键被按下时
keypress 按键被按下并被释放时
keyup 按键被释放时
事件特有属性
名称 说明 返回值
char 按键代表的字符 字符串
key 所按的键 字符串
altkey 是否按下alt/option键 boolean
shiftkey 是否按下shift键 boolean
ctrlkey 是否按下ctrl键 boolean
repeat 该建是否一直处于被按下的状态 boolean

事件参考

设计模式

设计模式的六大原则

  1. 开闭原则(Open Close Principle)

开闭原则的意思是:对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。简言之,是为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类,后面的具体设计中我们会提到这点。

  1. 里氏代换原则(Liskov Substitution Principle)

里氏代换原则是面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。LSP 是继承复用的基石,只有当派生类可以替换掉基类,且软件单位的功能不受到影响时,基类才能真正被复用,而派生类也能够在基类的基础上增加新的行为。里氏代换原则是对开闭原则的补充。实现开闭原则的关键步骤就是抽象化,而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。

  1. 依赖倒转原则(Dependence Inversion Principle)

这个原则是开闭原则的基础,具体内容:针对接口编程,依赖于抽象而不依赖于具体。

  1. 接口隔离原则(Interface Segregation Principle)

这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。它还有另外一个意思是:降低类之间的耦合度。由此可见,其实设计模式就是从大型软件架构出发、便于升级和维护的软件设计思想,它强调降低依赖,降低耦合。

  1. 迪米特法则,又称最少知道原则(Demeter Principle)

最少知道原则是指:一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。

  1. 合成复用原则(Composite Reuse Principle)

合成复用原则是指:尽量使用合成/聚合的方式,而不是使用继承。

控制反转

控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称DI),还有一种方式叫“依赖查找”(Dependency Lookup)。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体,将其所依赖的对象的引用传递给它。

Class A中用到了Class B的对象b,一般情况下,需要在A的代码中显示的new一个B的对象。

采用依赖注入技术之后,A的代码只需要定义一个私有的B对象,不需要直接new来获得这个对象,而是通过相关的容器控制程序来将B对象在外部new出来并注入到A类里的引用中。而具体获取的方法、对象被获取时的状态由配置文件(如XML)来指定。

  • 依赖倒置原则:实体必须依靠抽象而不是具体实现。它表示高层次的模块不应该依赖于低层次的模块,它们都应该依赖于抽象。在传统软件设计中,我们一般都是上层代码依赖下层代码,当下层代码变动时,我们上层代码要跟着变动,维护成本比较高。这时我们可以上层定义接口,下层来实现这个接口,从而使得下层依赖于上层,降低耦合度。

  • 依赖注入:核心思想是把类中所依赖单元的实例化过程放到类外面中去实现,然后把依赖注入进来。常用的依赖注入方式有属性注入和构造函数注入。

    • 基于接口。实现特定接口以供外部容器注入所依赖类型的对象。
    • 基于 set 方法。实现特定属性的public set方法,来让外部容器调用传入所依赖类型的对象。
    • 基于构造函数。实现特定参数的构造函数,在新建对象时传入所依赖类型的对象。
    • 基于注解。基于Java的注解功能,在私有变量前加“@Autowired”等注解,不需要显式的定义以上三种代码,便可以让外部容器传入对应的对象。该方案相当于定义了public的set方法,但是因为没有真正的set方法,从而不会为了实现依赖注入导致暴露了不该暴露的接口(因为set方法只想让容器访问来注入而并不希望其他依赖此类的对象访问)。
  • 依赖查找: 依赖查找更加主动,在需要的时候通过调用框架提供的方法来获取对象,获取时需要提供相关的配置文件路径、key等信息来确定获取对象的状态