1.call的实现
- 第一个参数为null或者undefined时,this指向全局对象window,值为原始值的指向该原始值的自动包装对象,如 String、Number、Boolean
- 为了避免函数名与上下文(context)的属性发生冲突,使用Symbol类型作为唯一值
- 将函数作为传入的上下文(context)属性执行
- 函数执行完成后删除该属性
- 返回执行结果
Function.prototype.myCall = function (context, ...args) { const cxt = context || window; // 将当前被调用的方法定义在cxt.func上.(为了能以对象调用形式绑定this) // 新建一个唯一的Symbol变量避免重复 const func = Symbol(); // this 是指调用该方法的函数,由于换成ctx调用了,所以该函数内部的this会变成ctx cxt[func] = this; args = args || [] // 以对象调用形式调用func, 此时this指向cxt也就是传入的需要绑定的this指向 const res = args.length > 0 ? cxt[func](...args) : cxt[func](); // 删除该方法,不然会对传入对象造成污染(添加该方法) delete cxt[func]; return res; }
2.apply的实现
- 前部分与call一样
- 第二个参数可以不传,但类型必须为数组或者类数组
Function.prototype.myApply = function (context, args = []) { let cxt = context || window; // 将当前被调用的方法定义在cxt.func上.(为了能以对象调用形式绑定this) // 新建一个唯一的Symbol变量避免重复 let func = Symbol(); cxt[func] = this; // 以对象调用形式调用func,此时this指向cxt也就是传入的需要绑定的this指向 const res = args.length > 0 ? cxt[func](...args) : cxt[func](); delete cxt[func]; return res; }
3.bind的实现
需要考虑:
- bind() 除了 this 外,还可传入多个参数;
- bind 创建的新函数可能传入多个参数;
- 新函数可能被当做构造函数调用;
- 函数可能有返回值;
实现方法:
- bind 方法不会立即执行,需要返回一个待执行的函数;(闭包)
- 实现作用域绑定(apply)
- 参数传递(apply 的数组传参)
- 当作为构造函数的时候,进行原型继承
Function.prototype.myBind = function (context, ...args) { // 新建一个变量赋值为this,表示当前函数 const fn = this // 判断有没有传参进来,若为空则赋值[] args = args ? args : [] // 返回一个newFn函数,在里面调用fn return function newFn(...newFnArgs) { if (this instanceof newFn) { return new fn(...args, ...newFnArgs) } return fn.apply(context, [...args, ...newFnArgs]) } }
- 测试
let name = '小王', age = 17 const obj = { name: '小张', age: this.age, myFun: function (from, to) { console.log(this.name + ' 年龄 ' + this.age + '来自 ' + from + '去往' + to) } } const db = { name: '德玛', age: 99 } // 结果 obj.myFun.myCall(db, '成都', '上海') // 德玛 年龄 99 来自 成都去往上海 obj.myFun.myApply(db, ['成都', '上海']) // 德玛 年龄 99 来自 成都去往上海 obj.myFun.myBind(db, '成都', '上海')() // 德玛 年龄 99 来自 成都去往上海 obj.myFun.myBind(db, ['成都', '上海'])() // 德玛 年龄 99 来自 成都, 上海去往 undefined
4.new的实现
原理:创建一个全新的对象,这个新对象的 __proto__ 会和原对象的原型相连接,这个新对象被绑定到函数调用的 this,如果函数没有返回对象,则返回创建的对象
- 一个继承自 Foo.prototype 的新对象被创建。
- 使用指定的参数调用构造函数 Foo,并将 this 绑定到新创建的对象。new Foo 等同于 new Foo(),也就是没有指定参数列表,Foo 不带任何参数调用的情况。
- 由构造函数返回的对象就是 new 表达式的结果。如果构造函数没有显式返回一个对象,则使用步骤1创建的对象。
- 一般情况下,构造函数不返回值,但是用户可以选择主动返回对象,来覆盖正常的对象创建步骤
function Ctor(){ .... } function myNew(ctor, ...args){ if(typeof ctor !== 'function'){ throw 'myNew function the first param must be a function'; } var newObj = Object.create(ctor.prototype); // 创建一个继承自ctor.prototype的新对象 var ctorReturnResult = ctor.apply(newObj, args); // 将构造函数ctor的this绑定到newObj中 var isObject = typeof ctorReturnResult === 'object' && ctorReturnResult !== null; var isFunction = typeof ctorReturnResult === 'function'; if(isObject || isFunction){ return ctorReturnResult; } return newObj; } let c = myNew(Ctor);
5.寄生式组合继承
function Person(obj) { this.name = obj.name this.age = obj.age } Person.prototype.add = function (value) { console.log(value) } var p1 = new Person({ name: "番茄", age: 18 }) function Person1(obj) { Person.call(this, obj) this.sex = obj.sex } // 这一步是继承的关键 Person1.prototype = Object.create(Person.prototype); Person1.prototype.constructor = Person1; Person1.prototype.play = function (value) { console.log(value) } var p2 = new Person1({ name: "鸡蛋", age: 118, sex: "男" })
6.ES6继承
// class 相当于es5中构造函数 // class中定义方法时,前后不能加function,全部定义在class的protopyte属性中 // class中定义的所有方法是不可枚举的 // class中只能定义方法,不能定义对象,变量等 // class和方法内默认都是严格模式 // es5中constructor为隐式属性 class People { constructor(name = 'wang', age = '27') { this.name = name this.age = age } eat() { console.log(`{this.age} eat food`) } } // 继承父类 class Woman extends People { constructor(name = 'ren', age = '27') { // 继承父类属性 super(name, age) } eat() { // 继承父类方法 super.eat() } } const wonmanObj = new Woman('xiaoxiami') wonmanObj.eat() // es5继承先创建子类的实例对象,然后再将父类的方法添加到this上(Parent.apply(this)) // es6继承是使用关键字super先创建父类的实例对象this,最后在子类class中修改this。
7.Ajax的实现
function ajax(url, method, body, headers) { return new Promise((resolve, reject) => { const req = new XMLHttpRequest() req.open(methods, url) for (const key in headers) { req.setRequestHeader(key, headers[key]) } req.onreadystatechange(() => { if (req.readystate == 4) { if (req.status >= '200' && req.status <= 300) { resolve(req.responeText) } else { reject(req) } } }) req.send(body) }) }
8.实现防抖函数(debounce)
- 防止手抖。连续触发在最后一次执行方法,场景:输入框匹配
const debounce = (fn, time = 1000) => { let timeLock = null return function (...args) { clearTimeout(timeLock) timeLock = setTimeout(() => { fn(...args) }, time) } }
9.实现节流函数(throttle)
- 在一定时间内只触发一次,场景:长列表滚动节流
function throttle(fn, delay = 500) { let flag = true; let timeId = null; return (...args) => { if (flag) { flag = false; setTimeout(() => { fn(...args); flag = true; }, delay); } }; } // 第二种 function throttle(callback: Function, delay: number = 500) { let lastTime = new Date().getTime() return () => { const nowTime = new Date().getTime() if (nowTime - lastTime > delay) { callback() lastTime = nowTime } } }
10.深拷贝(deepclone)
- 判断类型,正则和日期直接返回新对象
- 空或者非对象类型,直接返回原值
- 考虑循环引用,判断如果hash中含有直接返回hash中的值
- 新建一个相应的new obj.constructor加入hash
- 遍历对象递归(普通key和key是symbol情况)
function deepClone(obj, hash = new WeakMap()) { if (obj instanceof RegExp) return new RegExp(obj) if (obj instanceof Date) return new Date(obj) if (obj === null || typeof obj !== 'object') return obj // 循环引用的情况 if (hash.has(obj)) { return hash.get(obj) } // new 一个相应的对象 // obj为Array,相当于new Array() // obj为Object,相当于new Object() const constr = new obj.constructor() hash.set(obj, constr) for (const key in obj) { if (obj.hasOwnProperty(key)) { constr[key] = deepClone(obj[key], hash) } } // 考虑symbol的情况 const symbolObj = Object.getOwnPropertySymbols(obj) for (let i = 0; i < symbolObj.length; i++) { if (obj.hasOwnProperty(symbolObj[i])) { constr[symbolObj[i]] = deepClone(obj[symbolObj[i]], hash) } } return constr }
11.数组扁平化的实现(flat)
// flat() 方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回。 // flat参数为指定要提取嵌套数组的结构深度,默认值为 1 const arr = [1, 2, [3, 4, [5, [6]]]] console.log(arr.flat(Infinity)) // 用reduce实现 function fn(arr) { return arr.reduce((prev, cur) => { return prev.concat(Array.isArray(cur) ? fn(cur) : cur) }, []) }
12.函数柯里化
function curry (fn,...args) { let fnLen = fn.length, // 函数的length就表示为函数的形参个数 argsLen = args.length; // 对比函数的参数和当前传入参数 // 若参数不够就继续递归返回curry // 若参数够就调用函数返回相应的值 if(fnLen > argsLen) { return function(...arg2s) { return curry(fn,...args,...arg2s) } }else{ return fn(...args) } } function sumFn (a,b,c) { return a + b + c }; let sum = curry(sumFn); sum(2)(3)(5) // 10 sum(2,3)(5) // 10 // 另一种 function add(...nums1) { function sum(...nums2) { nums1 = [...nums1, ...nums2] return sum } sum.toString = function () { return nums1.reduce((pre, cur) => pre + cur, 0) } return sum }
13.使用闭包实现每隔一秒打印 1,2,3,4
for (var i = 1; i <= 5; i++) { (function (i) { setTimeout(() => console.log(i), 1000 * i) })(i) } function methodName (num, time = 1000) { for (let index = 1; index <= num; index++) { setTimeout(() => { console.log(index); }, time * index) } }
14.生成随机数的各种方法?
function getRandom(min, max) { return Math.floor(Math.random() * (max - min)) + min }
15.如何实现数组的随机排序?
const arr = [2, 3, 454, 34, 324, 32] arr.sort(randomSort) function randomSort(a, b) { return Math.random() > 0.5 ? -1 : 1 }
16.实现正则切分千分位(10000 => 10,000)
// 无小数点 const num1 = '1321434322222' num1.replace(/(\d)(?=(\d{3})+1,') // 有小数点 const num2 = '342243242322.3432423' num2.replace(/(\d)(?=(\d{3})+\.)/g,'$1,')
17.对象数组去重
输入: [{a:1, b:2, c:3}, {b:2, c:3, a:1}, {d:2, c:2}] 输出: [{a:1, b:2, c:3}, {d:2, c:2}]
- 首先写一个函数把对象中的key排序,然后再转成字符串
- 遍历数组利用Set将转为字符串后的对象去重
function objSort(obj) { const newObj = {} // 遍历对象,并将key进行排序 Object.keys(obj).sort().map(key => { newObj[key] = obj[key] }) // 将排序好的数组转成字符串 return JSON.stringify(newObj) } function unique(arr) { const set = new Set() for (let i = 0; i < arr.length; i++) { const str = objSort(arr[i]) set.add(str) } // 将数组中的字符串转回对象 arr = [...set].map(item => { return JSON.parse(item) }) return arr }
18.查找字符串中出现最多的字符和个数
- 例: abbcccddddd -> 字符最多的是d,出现了5次
let str = 'abcabcabcbbccccc' let num = 0 let char = '' // 使其按照一定的次序排列 str = str.split('').sort().join('') // "aaabbbbbcccccccc" // 定义正则表达式 const re = /(\w)\1+/g str.replace(re, (1) => { if (num < 0.length) { num = 0.length char = 1 } }) console.log(\`字符最多的是{char},出现了${num}次`)
19.图片懒加载
let imgList = [...document.querySelectorAll('img')] const length = imgList.length const imgLazyLoad = function () { let count = 0 return (function () { const deleteIndexList = [] imgList.forEach((img, index) => { const rect = img.getBoundingClientRect() if (rect.top < window.innerHeight) { img.src = img.dataset.src deleteIndexList.push(index) count++ if (count === length) { document.removeEventListener('scroll', imgLazyLoad) } } }) imgList = imgList.filter((img, index) => !deleteIndexList.includes(index)) })() } // 这里最好加上防抖处理 document.addEventListener('scroll', imgLazyLoad)
20.倒计时
async function timeOut(num) { let n = Math.floor(num); console.log(n--); while (n >= 0) { await new Promise((resolve) => { setTimeout(() => { console.log(n--); resolve(); }, 1000); }); } }
gsdsfsd
gsd
g
sg
s