1)事件注册
- 组件装载 / 更新。
- 通过lastProps、nextProps判断是否新增、删除事件分别调用事件注册、卸载方法。
- 调用EventPluginHub的enqueuePutListener进行事件存储
- 获取document对象。
- 根据事件名称(如onClick、onCaptureClick)判断是进行冒泡还是捕获。
- 判断是否存在addEventListener方法,否则使用attachEvent(兼容IE)。
- 给document注册原生事件回调为dispatchEvent(统一的事件分发机制)。
2)事件存储
- EventPluginHub负责管理React合成事件的callback,它将callback存储在listenerBank中,另外还存储了负责合成事件的Plugin。
- EventPluginHub的putListener方法是向存储容器中增加一个listener。
- 获取绑定事件的元素的唯一标识key。
- 将callback根据事件类型,元素的唯一标识key存储在listenerBank中。
- listenerBank的结构是:listenerBank[registrationName][key]。
{ onClick:{ nodeid1:()=>{...} nodeid2:()=>{...} }, onChange:{ nodeid3:()=>{...} nodeid4:()=>{...} } }
3)事件触发执行
- 触发document注册原生事件的回调dispatchEvent
- 获取到触发这个事件最深一级的元素 这里的事件执行利用了React的批处理机制
代码示例
<div onClick={this.parentClick} ref={ref => this.parent = ref}> <div onClick={this.childClick} ref={ref => this.child = ref}> test </div> </div>
- 首先会获取到this.child
- 遍历这个元素的所有父元素,依次对每一级元素进行处理。
- 构造合成事件。
- 将每一级的合成事件存储在eventQueue事件队列中。
- 遍历eventQueue。
- 通过isPropagationStopped判断当前事件是否执行了阻止冒泡方法。
- 如果阻止了冒泡,停止遍历,否则通过executeDispatch执行合成事件。
- 释放处理完成的事件。
4)合成事件
- 调用EventPluginHub的extractEvents方法。
- 循环所有类型的EventPlugin(用来处理不同事件的工具方法)。
- 在每个EventPlugin中根据不同的事件类型,返回不同的事件池。
- 在事件池中取出合成事件,如果事件池是空的,那么创建一个新的。
- 根据元素nodeid(唯一标识key)和事件类型从listenerBink中取出回调函数
- 返回带有合成事件参数的回调函数
链式调用的核心就在于调用完的方法将自身实例返回 1)示例一
function Class1() { console.log('初始化') } Class1.prototype.method = function(param) { console.log(param) return this } let cl = new Class1() //由于new 在实例化的时候this会指向创建的对象, 所以this.method这个方法会在原型链中找到。 cl.method('第一次调用').method('第二次链式调用').method('第三次链式调用')
2)示例二
var obj = { a: function() { console.log("a"); return this; }, b: function() { console.log("b"); return this; }, }; obj.a().b();
考点:函数柯里化
函数柯里化概念: 柯里化(Currying)是把接受多个参数的函数转变为接受一个单一参数的函数,并且返回接受余下的参数且返回结果的新函数的技术。
1)粗暴版
function add (a) { return function (b) { return function (c) { return a + b + c; } } } console.log(add(1)(2)(3)); // 6
2)柯里化解决方案
- 参数长度固定
const curry = (fn) => (judge = (...args) => args.length === fn.length ? fn(...args) : (...arg) => judge(...args, ...arg)); const add = (a, b, c) => a + b + c; const curryAdd = curry(add); console.log(curryAdd(1)(2)(3)); // 6 console.log(curryAdd(1, 2)(3)); // 6 console.log(curryAdd(1)(2, 3)); // 6
在 js 中经常会出现嵌套调用这种情况,如 a.b.c.d.e,但是这么写很容易抛出异常。你需要这么写 a && a.b && a.b.c && a.b.c.d && a.b.c.d.e,但是显得有些啰嗦与冗长了。特别是在 graphql 中,这种嵌套调用更是难以避免。 这时就需要一个 get 函数,使用 get(a, 'b.c.d.e') 简单清晰,并且容错性提高了很多。
1)代码实现
function get(source, path, defaultValue = undefined) { // a[3].b -> a.3.b -> [a,3,b] // path 中也可能是数组的路径,全部转化成 . 运算符并组成数组 const paths = path.replace(/\[(\d+)\]/g, ".$1").split("."); let result = source; for (const p of paths) { // 注意 null 与 undefined 取属性会报错,所以使用 Object 包装一下。 result = Object(result)[p]; if (result == undefined) { return defaultValue; } } return result; } // 测试用例 console.log(get({ a: null }, "a.b.c", 3)); // output: 3 console.log(get({ a: undefined }, "a", 3)); // output: 3 console.log(get({ a: null }, "a", 3)); // output: 3 console.log(get({ a: [{ b: 1 }] }, "a[0].b", 3)); // output: 1
2)代码实现 不考虑数组的情况
const _get = (object, keys, val) => { return keys.split(/\./).reduce( (o, j)=>( (o || {})[j] ), object ) || val } console.log(get({ a: null }, "a.b.c", 3)); // output: 3 console.log(get({ a: undefined }, "a", 3)); // output: 3 console.log(get({ a: null }, "a", 3)); // output: 3 console.log(get({ a: { b: 1 } }, "a.b", 3)); // output: 1
1)伪类(pseudo-classes)
- 其核⼼就是⽤来选择DOM树之外的信息,不能够被普通选择器选择的⽂档之外的元素,⽤来添加⼀些选择器的特殊效果。
- ⽐如
等 - 由于状态的变化是⾮静态的,所以元素达到⼀个特定状态时,它可能得到⼀个伪类的样式;当状态改变时,它⼜会失去这个样式。
- 由此可以看出,它的功能和class有些类似,但它是基于⽂档之外的抽象,所以叫 伪类。
2)伪元素(Pseudo-elements)
- DOM树没有定义的虚拟元素
- 核⼼就是需要创建通常不存在于⽂档中的元素,
- ⽐如::before ::after 它选择的是元素指定内容,表示选择元素内容的之前内容或之后内容。
- 伪元素控制的内容和元素是没有差别的,但是它本身只是基于元素的抽象,并不存在于⽂档中,所以称为伪元素。⽤于将特殊的效果添加到某些选择器
2)伪类与伪元素的区别
表示⽅法
- CSS2 中伪类、伪元素都是以单冒号:表示,
- CSS2.1 后规定伪类⽤单冒号表示,伪元素⽤双冒号::表示,
- 浏览器同样接受 CSS2 时代已经存在的伪元素(
, , �line, 等)的单冒号写法。 - CSS2 之后所有新增的伪元素(如::selection),应该采⽤双冒号的写法。
- CSS3中,伪类与伪元素在语法上也有所区别,伪元素修改为以::开头。浏览器对以:开头的伪元素也继续⽀持,但建议规范书写为::开头
定义不同
- 伪类即假的类,可以添加类来达到效果
- 伪元素即假元素,需要通过添加元素才能达到效果
总结:
- 伪类和伪元素都是⽤来表示⽂档树以外的"元素"。
- 伪类和伪元素分别⽤单冒号:和双冒号::来表示。
- 伪类和伪元素的区别,关键点在于如果没有伪元素(或伪类),
- 是否需要添加元素才能达到效果,如果是则是伪元素,反之则是伪类。
4)相同之处:
- 伪类和伪元素都不出现在源⽂件和DOM树中。也就是说在html源⽂件中是看不到伪类和伪元素的。 不同之处:
- 伪类其实就是基于普通DOM元素⽽产⽣的不同状态,他是DOM元素的某⼀特征。
- 伪元素能够创建在DOM树中不存在的抽象对象,⽽且这些抽象对象是能够访问到的。