目录
实现 Promise
实现一个 call 函数
实现一个 apply 函数
实现一个 bind 函数
浅拷贝、深拷贝的实现
实现一个防抖函数
实现一个节流函数
柯里化函数的实现
Object.create 的基本实现原理
实现一个双向数据绑定
实现一个简单路由
实现懒加载
rem 基本设置
手写实现 AJAX
手写jsonp
实现数组的map方法
实现 Promise
// 未添加异步处理等其他边界情况// ①自动执行函数,②三个状态,③thenclass Promise {constructor (fn) {// 三个状态this.state = 'pending'this.value = undefinedthis.reason = undefinedlet resolve = value => {if (this.state === 'pending') {this.state = 'fulfilled'this.value = value}}let reject = value => {if (this.state === 'pending') {this.state = 'rejected'this.reason = value}}// 自动执行函数try {fn(resolve, reject)} catch (e) {reject(e)}}// thenthen(onFulfilled, onRejected) {switch (this.state) {case 'fulfilled':onFulfilled(this.value)breakcase 'rejected':onRejected(this.reason)breakdefault:}}}
实现一个 call 函数
// 思路:将要改变this指向的方法挂到目标this上执行并返回Function.prototype.mycall = function (context) {if (typeof this !== 'function') {throw new TypeError('not funciton')}context = context || windowcontext.fn = thislet arg = [...arguments].slice(1)let result = context.fn(...arg)delete context.fnreturn result}
实现一个 apply 函数
// 思路:将要改变this指向的方法挂到目标this上执行并返回Function.prototype.myapply = function (context) {if (typeof this !== 'function') {throw new TypeError('not funciton')}context = context || windowcontext.fn = thislet resultif (arguments[1]) {result = context.fn(...arguments[1])} else {result = context.fn()}delete context.fnreturn result}
实现一个 bind 函数
// 思路:类似call,但返回的是函数Function.prototype.mybind = function (context) {if (typeof this !== 'function') {throw new TypeError('Error')}let _this = thislet arg = [...arguments].slice(1)return function F() {// 处理函数使用new的情况if (this instanceof F) {return new _this(...arg, ...arguments)} else {return _this.apply(context, arg.concat(...arguments))}}}
浅拷贝、深拷贝的实现
浅拷贝:
// 1. ...实现let copy1 = {...{x:1}}// 2. Object.assign实现let copy2 = Object.assign({}, {x:1})
深拷贝:
// 1. JOSN.stringify()/JSON.parse() // 缺点:拷贝对象包含 正则表达式,函数,或者undefined等值会失败let obj = {a: 1, b: {x: 3}}JSON.parse(JSON.stringify(obj))// 2. 递归拷贝function deepClone(obj) {let copy = obj instanceof Array ? [] : {}for (let i in obj) {if (obj.hasOwnProperty(i)) {copy[i] = typeof obj[i] === 'object' ? deepClone(obj[i]) : obj[i]}}return copy}
实现一个防抖函数
// 思路:在规定时间内未触发第二次,则执行function debounce (fn, delay) {// 利用闭包保存定时器let timer = nullreturn function () {let context = thislet arg = arguments// 在规定时间内再次触发会先清除定时器后再重设定时器clearTimeout(timer)timer = setTimeout(function () {fn.apply(context, arg)}, delay)}}function fn () {console.log('防抖')}addEventListener('scroll', debounce(fn, 1000))
实现一个节流函数
// 基础版1:时间戳(第一次触发会执行,但不排除不执行的可能,请思考一下哦)function throttle(fn, delay) {var prev = Date.now()return function(...args) {var dist = Date.now() - previf (dist >= delay) {fn.apply(this, args)prev = Date.now()}}}// 基础版2:定时器(最后一次也会执行)function throttle(fn, delay) {var timer = nullreturn function(...args) {var that = thisif(!timer) {timer = setTimeout(function() {fn.apply(this, args)timer = null}, delay)}}}// 进阶版:开始执行、结束执行function throttle(fn, delay) {var timer = nullvar prev = Date.now()return function(...args) {var that = thisvar remaining = delay - (Date.now() - prev) // 剩余时间if (remaining <= 0) { // 第 1 次触发fn.apply(that, args)prev = Date.now()} else { // 第 1 次之后触发timer && clearTimeout(timer)timer = setTimeout(function() {fn.apply(that, args)}, remaining)}}}function fn () {console.log('节流')}addEventListener('scroll', throttle(fn, 1000))
柯里化函数的实现
柯里化函数的定义:将多参数的函数转换成单参数的形式。
柯里化函数实现的原理:利用闭包原理在执行可以形成一个不销毁的作用域,然后把需要预先处理的内容都储存在这个不销毁的作用域中,并且返回一个最少参数函数。
第一种:固定传入参数,参数够了才执行
/*** 实现要点:柯里化函数接收到足够参数后,就会执行原函数,那么我们如何去确定何时达到足够的参数呢?* 柯里化函数需要记住你已经给过他的参数,如果没给的话,则默认为一个空数组。* 接下来每次调用的时候,需要检查参数是否给够,如果够了,则执行fn,没有的话则返回一个新的 curry 函数,将现有的参数塞给他。* */// 待柯里化处理的函数let sum = (a, b, c, d) => {return a + b + c + d}// 柯里化函数,返回一个被处理过的函数 let curry = (fn, ...arr) => { // arr 记录已有参数return (...args) => { // args 接收新参数if (fn.length <= (...arr,...args)) { // 参数够时,触发执行return fn(...arr, ...args)} else { // 继续添加参数return curry(fn, [...arr, ...args])}}}var sumPlus = curry(sum)sumPlus(1)(2)(3)(4)sumPlus(1, 2)(3)(4)sumPlus(1, 2, 3)(4)
第二种:不固定传入参数,随时执行
/*** 当然了,柯里化函数的主要作用还是延迟执行,执行的触发条件不一定是参数个数相等,也可以是其他的条件。* 例如参数个为0的情况,那么我们需要对上面curry函数稍微做修改*/// 待柯里化处理的函数let sum = arr => {return arr.reduce((a, b) => {return a + b})}let curry = (fn, ...arr) => { // arr 记录已有参数return (...args) => { // args 接收新参数if (args.length === 0) { // 参数为空时,触发执行return fn(...arr, ...args)} else { // 继续添加参数return curry(fn, ...arr, ...args)}}}var sumPlus = curry(sum)sumPlus(1)(2)(3)(4)()sumPlus(1, 2)(3)(4)()sumPlus(1, 2, 3)(4)()
Object.create 的基本实现原理
// 思路:将传入的对象作为原型function create(obj) {function F() {}F.prototype = objreturn new F()}
实现一个双向数据绑定
let obj = {}let input = document.getElementById('input')let span = document.getElementById('span')// 数据劫持Object.defineProperty(obj, 'text', {configurable: true,enumerable: true,get() {console.log('获取数据了')},set(newVal) {console.log('数据更新了')input.value = newValspan.innerHTML = newVal}})// 输入监听input.addEventListener('keyup', function(e) {obj.text = e.target.value})
实现一个简单路由
// hash路由class Route{constructor(){// 路由存储对象this.routes = {}// 当前hashthis.currentHash = ''// 绑定this,避免监听时this指向改变this.freshRoute = this.freshRoute.bind(this)// 监听window.addEventListener('load', this.freshRoute, false)window.addEventListener('hashchange', this.freshRoute, false)}// 存储storeRoute (path, cb) {this.routes[path] = cb || function () {}}// 更新freshRoute () {this.currentHash = location.hash.slice(1) || '/'this.routes[this.currentHash]()}}
实现懒加载
<ul><li><img src="./imgs/default.png" data="./imgs/1.png" alt=""></li><li><img src="./imgs/default.png" data="./imgs/2.png" alt=""></li><li><img src="./imgs/default.png" data="./imgs/3.png" alt=""></li><li><img src="./imgs/default.png" data="./imgs/4.png" alt=""></li><li><img src="./imgs/default.png" data="./imgs/5.png" alt=""></li><li><img src="./imgs/default.png" data="./imgs/6.png" alt=""></li><li><img src="./imgs/default.png" data="./imgs/7.png" alt=""></li><li><img src="./imgs/default.png" data="./imgs/8.png" alt=""></li><li><img src="./imgs/default.png" data="./imgs/9.png" alt=""></li><li><img src="./imgs/default.png" data="./imgs/10.png" alt=""></li></ul>
let imgs = document.querySelectorAll('img')// 可视区高度let clientHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeightfunction lazyLoad () {// 滚动卷去的高度let scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTopfor (let i = 0; i < imgs.length; i ++) {// 图片在可视区冒出的高度let x = clientHeight + scrollTop - imgs[i].offsetTop// 图片在可视区内if (x > 0 && x < clientHeight+imgs[i].height) {imgs[i].src = imgs[i].getAttribute('data')}}}// addEventListener('scroll', lazyLoad) or setInterval(lazyLoad, 1000)
rem 基本设置
// 提前执行,初始化 resize 事件不会执行setRem()// 原始配置function setRem () {let doc = document.documentElementlet width = doc.getBoundingClientRect().widthlet rem = width / 75doc.style.fontSize = rem + 'px'}// 监听窗口变化addEventListener("resize", setRem)
手写实现 AJAX
// 1. 简单流程// 实例化let xhr = new XMLHttpRequest()// 初始化xhr.open(method, url, async)// 发送请求xhr.send(data)// 设置状态变化回调处理请求结果xhr.onreadystatechange = () => {if (xhr.readyStatus === 4 && xhr.status === 200) {console.log(xhr.responseText)}}// 2. 基于promise实现function ajax (options) {// 请求地址const url = options.url// 请求方法const method = options.method.toLocaleLowerCase() || 'get'// 默认为异步trueconst async = options.async// 请求参数const data = options.data// 实例化const xhr = new XMLHttpRequest()// 请求超时if (options.timeout && options.timeout > 0) {xhr.timeout = options.timeout}// 返回一个Promise实例return new Promise ((resolve, reject) => {xhr.ontimeout = () => reject && reject('请求超时')// 监听状态变化回调xhr.onreadystatechange = () => {if (xhr.readyState == 4) {// 200-300 之间表示请求成功,304资源未变,取缓存if (xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) {resolve && resolve(xhr.responseText)} else {reject && reject()}}}// 错误回调xhr.onerror = err => reject && reject(err)let paramArr = []let encodeData// 处理请求参数if (data instanceof Object) {for (let key in data) {// 参数拼接需要通过 encodeURIComponent 进行编码paramArr.push(encodeURIComponent(key) + '=' + encodeURIComponent(data[key]))}encodeData = paramArr.join('&')}// get请求拼接参数if (method === 'get') {// 检测url中是否已存在 ? 及其位置const index = url.indexOf('?')if (index === -1) url += '?'else if (index !== url.length -1) url += '&'// 拼接urlurl += encodeData}// 初始化xhr.open(method, url, async)// 发送请求if (method === 'get') xhr.send(null)else {// post 方式需要设置请求头xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded;charset=UTF-8')xhr.send(encodeData)}})}
手写jsonp
$.ajax({url: 'xxxxx', //一个跨域的urltype: 'get',dataType: 'jsonp', //设置服务器返回的数据类型jsonp: 'onJsonPLoad', //这个值用来配置前面提过的callback,它会拼接到url的后面jsonpCallback: 'handleRes', //用来设置回调函数名称success: function (res){ //这里的success回调就相当于之前写到的handleRes方法。console.log(res);}})
实现数组的map方法
Array.prototype.myMap = function(fn, thisValue) {let res = []thisValue = thisValue||[]let arr = thisfor(let i in arr) {res.push(fn(arr[i]))}return res}