自定义事件
面试官:手写一个自定义原生事件。
简单三步曲:
创建自定义事件:
const myEvent = new Event('jsliangEvent')
监听自定义事件:
document.addEventListener(jsliangEvent)
触发自定义事件:
document.dispatchEvent(jsliangEvent)
简单实现:
window.onload = function() { const myEvent = new Event('jsliangEvent'); document.addEventListener('jsliangEvent', function(e) { console.log(e); }) setTimeout(() => { document.dispatchEvent(myEvent); }, 2000); };复制代码
页面 2 秒后自动触发 myEvent
事件。
创建自定义事件
创建自定义事件的 3 种方法:
使用
Event
let myEvent = new Event('event_name');复制代码
使用
customEvent
(可以传参数)
let myEvent = new CustomEvent('event_name', { detail: { // 将需要传递的参数放到这里 // 可以在监听的回调函数中获取到:event.detail } });复制代码
使用
document.createEvent('CustomEvent')
和initEvent()
// createEvent:创建一个事件let myEvent = document.createEvent('CustomEvent'); // 注意这里是 CustomEvent// initEvent:初始化一个事件myEvent.initEvent( // 1. event_name:事件名称 // 2. canBubble:是否冒泡 // 3. cancelable:是否可以取消默认行为)复制代码
事件的监听
自定义事件的监听其实和普通事件一样,通过 addEventListener
来监听:
button.addEventListener('event_name', function(e) {})复制代码
事件的触发
触发自定义事件使用 dispatchEvent(myEvent)
。
注意,这里的参数是要自定义事件的对象(也就是 myEvent
),而不是自定义事件的名称(myEvent
)
案例
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>自定义事件</title></head><body> <button class="btn">点我</button> <script> window.onload = function() { // 方法 1 const myEvent = new Event('myEvent'); // 方法 2 // const myEvent = new CustomEvent('myEvent', { // detail: { // name: 'jsliang', // }, // }); // 方法 3 // const myEvent = document.createEvent('CustomEvent'); // myEvent.initEvent('myEvent', true, true); const btn = document.querySelector('.btn'); btn.addEventListener('myEvent', function(e) { console.log(e); }) setTimeout(() => { btn.dispatchEvent(myEvent); }, 2000); }; </script></body></html>复制代码
上面 console.log(e)
输出:
/* CustomEvent { bubbles: true cancelBubble: false cancelable: true composed: false currentTarget: null defaultPrevented: false detail: null eventPhase: 0 isTrusted: false path: (5) [button.btn, body, html, document, Window] returnValue: true srcElement: button.btn target: button.btn timeStamp: 16.354999970644712 type: "myEvent" } */复制代码
Object.create()
Object.create()
方法创建一个新对象,使用现有的对象来提供新创建的对象的 __proto__
。
function create(proto) { function F() {}; F.prototype = proto; return new F(); }复制代码
试验一下:
function create(proto) { function F() {}; F.prototype = proto; return new F(); }const Father = function() { this.bigName = '爸爸'; }; Father.prototype.sayHello = function() { console.log(`我是你${this.bigName}`); }const Child = function() { Father.call(this); this.smallName = '儿子'; } Child.prototype = create(Father.prototype); Child.prototype.constructor = Child;const child = new Child(); child.sayHello(); // 我是你爸爸复制代码
下面讲寄生组合式继承会用到 Object.create()
。
ES5 实现类继承
使用 ES5 实现继承,简要在 3 行代码:
Father.call(this)
。在Child
中通过Father.call(this)
,将Father
的this
修改为Child
的this
Child.prototype = Object.create(Father.prototype)
。将Child
的原型链绑定到Father
的原型链上。Child.prototype.constructor = Child
。这个构造函数的实例的构造方法constructor
指向自身。
const Father = function (name, like) { this.name = name; this.like = like; this.money = 10000000; }; Father.prototype.company = function() { console.log(`${this.name} 有 ${this.money} 元`); }const Children = function (name, like) { Father.call(this); this.name = name; this.like = like; } Children.prototype = Object.create(Father.prototype); Children.prototype.constructor = Children;const jsliang = new Children('jsliang', '学习');console.log(jsliang); // Children {name: "jsliang", like: "学习", money: 10000000}jsliang.company(); // jsliang 有 10000000 元复制代码
需要注意 Child.prototype = Object.create(Father.prototype)
这句话:
这一步不用
Child.prototype = Father.prototype
的原因是怕共享内存,修改父类原型对象就会影响子类不用
Child.prototype = new Parent()
的原因是会调用 2 次父类的构造方法(另一次是call
),会存在一份多余的父类实例属性Object.create
是创建了父类原型的副本,与父类原型完全隔离
最后,这种继承方法,叫做 寄生组合式继承。
instanceof
面试官:手写一个 instanceof
,其实 instanceof
就是查找原型链的过程.
那么有下面代码:
const Father = function() { this.bigName = '爸爸'; }; Father.prototype.sayHello = function() { console.log(`我是你${this.bigName}`); }const Child = function() { Father.call(this); this.smallName = '儿子'; } Child.prototype = Object.create(Father.prototype); Child.prototype.constructor = Child;const child = new Child(); child.sayHello(); // 我是你爸爸console.log(child instanceof Child); // trueconsole.log(child instanceof Father); // trueconsole.log(child instanceof Object); // true复制代码
如何改造当中的 instanceof
呢?
function instanceOf(a, b) { let proto = a.__proto__; const prototype = b.prototype; // 从当前 __proto__ 开始查找 while (proto) { // 如果找到 null 还没有找到,返回 false if (proto === null) { return false; } // 如果 a.__proto__.xxx === b.prototype,返回 true if (proto === prototype) { return true; } // 进一步迭代 proto = proto.__proto__; } }console.log(instanceOf(child, Child)); // trueconsole.log(instanceOf(child, Father)); // trueconsole.log(instanceOf(child, Object)); // true复制代码
输出结果同 instanceof
一样,完成目标!
才怪!!!
经过测试:
let num = 123;console.log(num instanceof Object); // falseconsole.log(instancOf(123, Object)); // true复制代码
为什么呢?因为 instanceof
在原生代码上,实际是做了基本类型的检测,基本类型应该返回 false
,所以可以进行改造:
function instanceOf(a, b) { // 新增:通过 typeof 判断基本类型 if (typeof a !== 'object' || b === null) { return false; } // 新增:getPrototypeOf 是 Object 自带的一个方法 // 可以拿到参数的原型对象 let proto = Object.getPrototypeOf(a); const prototype = b.prototype; while (proto) { if (proto === null) { return false; } if (proto === prototype) { return true; } proto = proto.__proto__; } }复制代码
柯里化
实现一个 add
方法,使计算结果能够满足以下预期:
add(1)(2)(3) = 6; add(1, 2, 3)(4) = 10; add(1)(2)(3)(4)(5) = 15;复制代码
实现方法:
function add () { const numberList = Array.from(arguments); // 进一步收集剩余参数 const calculate = function() { numberList.push(...arguments); return calculate; } // 利用 toString 隐式转换,最后执行时进行转换 calculate.toString = function() { return numberList.reduce((a, b) => a + b, 0); } return calculate; }// 实现一个 add 方法,使计算结果能够满足以下预期console.log(add(1)(2)(3)); // 6console.log(add(1, 2, 3)(4)); // 10;console.log(add(1)(2)(3)(4)(5)); // 15;复制代码
详细看 JavaScript 系列的闭包篇章,里面有讲解到闭包和柯里化。
迭代器
迭代器的意思是:我的版本是可控的,你踢我一下,我动一下。
// 在数据获取的时候没有选择深拷贝内容// 对于引用类型进行处理会有问题// 这里只是演示简化了一点function Iterdtor(arr) { let data = []; if (!Array.isArray(arr)) { data = [arr]; } else { data = arr; } let length = data.length; let index = 0; // 迭代器的核心 next // 当调用 next 的时候会开始输出内部对象的下一项 this.next = function () { let result = {}; result.value = data[index]; result.done = index === length - 1 ? true : false; if (index !== length) { index++; return result; } // 当内容已经没有了的时候返回一个字符串提示 return 'data is all done'; }; }const arr = [1, 2, 3];// 生成一个迭代器对象const iterdtor = new Iterdtor(arr);console.log(iterdtor.next()); // { value: 1, done: false }console.log(iterdtor.next()); // { value: 2, done: false }console.log(iterdtor.next()); // { value: 2, done: true }console.log(iterdtor.next()); // data is all done复制代码
Ajax
通过 Promise
实现 ajax
:
index.json
{ "name": "jsliang", "age": 25}复制代码
index.js
const getData = (url) => { return new Promise((resolve, reject) => { // 设置 XMLHttpRequest 请求 const xhr = new XMLHttpRequest(); // 设置请求方法和 url xhr.open('GET', url); // 设置请求头 xhr.setRequestHeader('Accept', 'application/json'); // 设置请求的时候,readyState 属性变化的一个监控 xhr.onreadystatechange = (res) => { // 如果请求的 readyState 不为 4,说明还没请求完毕 if (xhr.readyState !== 4) { return; } // 如果请求成功(200),那么 resolve 它,否则 reject 它 if (xhr.status === 200) { resolve(xhr.responseText); } else { reject(new Error(xhr.responseText)); } }; // 发送请求 xhr.send(); }) }; getData('./index.json').then((res) => { console.log(res); // { "name": "jsliang", "age": 25 }})复制代码
补充:Ajax 状态
0 - 未初始化。尚未调用
open()
方法1 - 启动。已经调用
open()
方法,但尚未调用send()
方法。2 - 发送。已经调用
send()
方法,但尚未接收到响应。3 - 接收。已经接收到部分响应数据。
4 - 完成。已经接收到全部响应数据,而且已经可以在客户端使用了。
数组扁平化
方法一:手撕递归
const jsliangFlat = (arr) => { // 1. 设置空数组 const result = []; // 2. 设置递归 const recursion = (tempArr) => { // 2.1 遍历数组 for (let i = 0; i < tempArr.length; i++) { // 2.2 如果数组里面还是一个数组,那么递归它 if (Array.isArray(tempArr[i])) { recursion(tempArr[i]); } else { // 2.3 否则添加它 result.push(tempArr[i]); } } }; recursion(arr); // 3. 返回结果 return result; };console.log(jsliangFlat([1, [2, [3, [4, [5]]]]])); // [1, 2, 3, 4, 5]复制代码
方法二:flat()
flat
方法可以扁平数组,如果不传参数,flat()
扁平一层,flat(2)
扁平 2 层,到 flat(Infinity)
扁平所有层。
const jsliangFlat = (arr) => { return arr.flat(Infinity); };console.log(jsliangFlat([1, [2, [3, [4, [5]]]]])); // [1, 2, 3, 4, 5]复制代码
注意这个方法在 Node
执行会报错,这是一个 ES6 的方法。
方法三:reduce
不推荐 reduce
,我怕小伙伴看得头晕。
const jsliangFlat = (arr = []) => { return arr.reduce((prev, next) => { if (Array.isArray(next)) { return prev.concat(jsliangFlat(next)); } else { return prev.concat(next); } }, []) };console.log(jsliangFlat([1, [2, [3, [4, [5]]]]])); // [1, 2, 3, 4, 5]复制代码
对象扁平化
其实我也不知道这个有没考,也不是很难:
const obj = { a: { b: { c: 1, d: 2, }, e: 3, }, f: { g: 4, h: { i: 5, }, }, };// 1. 设置结果集const result = [];// 2. 递归const recursion = (obj, path = []) => { // 2.1 如果到底部,此时 obj 是对应的值 if (typeof obj !== 'object') { // 2.1.1 结果集加上这个字段 result.push({ [path.join('.')]: obj, }) // 2.1.2 终止递归 return; } // 2.2 遍历 obj 对象 for (let i in obj) { // 2.2.1 判断对象自身是否含有该字段(排除原型链) if (obj.hasOwnProperty(i)) { // 2.2.2 回溯,添加路径 path.push(i); // 2.2.3 进一步递归 recursion(obj[i], path); // 2.2.4 回溯,删除路径,方便下一次使用 path.pop(); } } }; recursion(obj);// 3. 返回结果console.log(result);/* [ { 'a.b.c': 1 }, { 'a.b.d': 2 }, { 'a.e': 3 }, { 'f.g': 4 }, { 'f.h.i': 5 }, ] */复制代码
还有反向推题:
根据
obj
和路径path
(a.b.c
),找到它的值
递归一下就行了,或者迭代也可以,不难。
写不出来的小哥反省下,写不出来的小姐姐找我,教你啊~ /手动狗头防暴力
数组去重
看着本文标题是 3 种,实际上有 5 种。
方法一:手撕去重
const jsliangSet = (arr) => { // 设置结果 const result = []; // 遍历数组 for (let i = 0; i < arr.length; i++) { // 如果结果集不包含这个元素 // 这里也可以用 result.indexOf(arr[i]) === -1 // 或者 arr.lastIndexOf(arr[i]) === i if (!result.includes(arr[i])) { result.push(arr[i]); } } // 返回结果 return result; };console.log(jsliangSet([1, 1, 1, 2, 2])); // [1, 2]复制代码
方法二:Set
const jsliangSet = (arr) => { return [...new Set(arr)]; };console.log(jsliangSet([1, 1, 1, 2, 2])); // [1, 2]
方法三:filter
同样,通过 filter
也可以,其实内核也是 lastIndexOf
和当前索引值的一个比对。
const jsliangSet = (arr) => { return arr.filter((item, index) => { return arr.lastIndexOf(item) === index; }) };console.log(jsliangSet([1, 1, 1, 2, 2])); // [1, 2]复制代码
其他
其他的还有:
发布订阅模式:Node 回调函数、Vue event bus
异步并发数限制
异步串行|异步并行
图片懒加载
滚动加载
数组 API 实现:
filter
、map
、forEach
、reduce
大数据渲染(渲染几万条数据不卡页面)
JSON:
JSON.parse()
、JSON.stringify()
作者:jsliang
共同学习,写下你的评论
评论加载中...
作者其他优质文章