实现一些已有算法的第一步是知道这个方法怎么用,第二步是实现原理,第三步是实现,第四步是测试实现的算法是否与原来算法一致,这个算法要干啥,入参出参数什么,接下来的js算法中将从这四步实现
实现 new
step1
箭头函数只针对非箭头函数,箭头函数不能使用new,因为箭头函数没有自身的this和prototype属性,箭头函数不绑定自己的this,他会捕获上下文中的this,值作为自己的 this。在使用 new 操作符时,会创建一个新的对象,并将构造函数内部的 this 绑定到这个新对象。然而,箭头函数没有自己的 this,因此无法与 new 操作符一起使用。使用 new 操作符创建对象时,新的对象会继承构造函数的原型属性。然而,箭头函数没有 prototype 属性,因此不能通过 new 操作符使用。
//第一种没有显示返回值
const test = function(){
console.log(this)//test{}
this.a = 444;
}
const newObj = new test()
console.log(newObj)//{ a: 444 }
//第二种有显示返回值
const returnTest = function(){
this.b = 333;
return {c:3}
}
const returnNewObj = new test()
console.log(returnNewObj)//{c:3}
step2:
- 创建一个新对象
- 将构造函数的this绑定到新对象上
- 执行构造函数代码
- 返回新对象
step:3 实现
function myNew(constructor,...args){
//1、创建一个新对象,为什么使用Object.create()不适用{},
//因为Object.create()可以给生成对象指定__proto__ = constructor.prototype,继承constructor原型
const obj = Object.create(constructor.prototype)
//2、绑定this,并执行代码
const result = constructor.apply(obj,args)
//3、返回新对象
return result instanceof Object? result:obj
}
step3:使用myNew测试开始的示例
const test = function(){
console.log(this)//test{}
this.a = 444;
}
const newObj = myNew(test)
console.log(newObj)//{ a: 444 }
//第二种有显示返回值
const returnTest = function(){
this.b = 333;
return {c:3}
}
const returnNewObj = myNew(returnTest)
console.log(returnNewObj)//{c:3}
实现call、apply、bind
问题:为什么会有call、apply和bind
普通的函数(非箭头函数)中的this的指向是在函数被调用时的调用者,也就是执行上下文被创建时确定
step1:call的使用方法
function greet(greeting, punctuation){
console.log(greeting + ', ' + this.name + punctuation);
}
const person = { name: 'Alice' };
greet.call(person, 'Hello', '!'); // 输出:Hello, Alice!
step2:实现原理
func.call(ctx,arg1,...)
//等价于以下代码
ctx.fn=func;
ctx.fn(arg1,....)
step3:实现
//函数的调用,需要添加在原型上
Function.prototype.myCall=function(context,...args){
//对this进行类型判断,如果不是function类型,就报错
//this应该指向的是调用myCall函数的对象(function也属于对象object类型)
//因为myCall的调用模式是上文提到的‘方法调用模式’
if(typeof this != 'function'){
throw new TypeError('type error')
}
// 不传的话,默认上下文是window
var context = context || window
// 假如context上面有fn属性的时候,会出现冲突
// 所以当context上面有fn属性的时候,要先保存一下
var temp = null
if (context.fn) {
temp = context.fn
}
// 给context创建一个fn属性,并将值设置为需要调用的函数(即this)
context.fn = this
//调用函数
const res = context.fn(...args)
// 删除context对象上的fn属性
if (temp) {
context.fn = temp
} else {
delete context.fn
}
// 返回运行结果
return res
}
step4:测试
greet.myCall(person, 'Hello', '!'); // 输出:Hello, Alice!
call和apply的传参方式不同其他都相同
Function.prototype.myApply=function(context) {
if( typeof this !== 'function' ){
throw new TypeError('Error');
}
context = context || window;
let temp = null;
if(context.fn){
temp = context.fn;
}else{
context.fn = this;
}
context.fn = this;
console.log(arguments);
const result = arguments[1] ? context.fn(...arguments[1]) : context.fn();
if(temp){
context.fn = temp;
}else{
delete context.fn;
}
return result;
}
step4:测试
let num=1;
let obj={
num:2,
fn:'this is obj.fn'
}
function test(a,b,c,d){
console.log(this.num,'test参数:',a,b,c,d)
}
console.log(obj,'obj')
// 调用myCall函数
test.myApply(obj,[1,2,3,4])
// 检查obj本身的fn是否被修改
console.log(obj.fn)
bind与call和apply的区别是bind不会立即执行只会更改this的指向,在绑定时可以传递参数,
Function.prototype.myBind = function (context,...args0){
//首先判断this是否是函数
if(typeof this != 'function'){
throw new TypeError('type Error');
}
let fn = this;
return function Fn(...args){
if(this instanceof Fn){
return new fn(...args0,...args)
}else{
return fn.call(context, ...args0, ...args)
}
}
}
step4:测试
function Point(x, y) {
this.x = x;
this.y = y;
}
// 情况1:正常调用bind函数
var testObj = {};
var YAxisPoint = Point.myBind(testObj, 0 );
YAxisPoint(1)
console.log(testObj)
// 情况2:bind返回函数作为构造函数
// 此时之前绑定的指向testObj的this会失效,会重新指向新的对象实例,但是参数会继续有效
let newObj=new YAxisPoint(2);
console.log('newObj',newObj)
实现instanceof
instanceof 用来判断 A的__proto__是不是B的prototype,只用于检查对象,不能检查基本类型,检查类型需要配合typeof
step1:
const d = new Date();
d instanceof Object//true
const string = '';
string instanceof String;//false
const stringObj = new String('string Object');
stringObj instanceof String//true
step2:
instanceof 在原型连上查找 A.proto == B.prototype
step3:
function myInstanceof(obj,Constructor){
while(obj){
//由制造原型链的new决定的 new中 obj = Object.create(Constructor.prototype)
if(obj.__proto__ == Constructor.prototype) return true;
obj = obj.__proto__;//去obj的下一级查找
}
return false
}
step4:
myInstanceof({},Object);
实现Object.keys()、Object.values(),Object.entries()
Object是不可迭代的,因此不能使用for of遍历,需要使用 for in 实现,
实现Object.Keys()
//实现Object.keys()
//入参是一个对象
function objectKeys(obj){
//出参事一个数组
const keys = [];
for(const item in obj){
if(obj.hasOwnProperty(item)){
keys.push(item)
}
}
return keys
}
实现Object.values
function objectValues(obj){
const values = [];
for(const item in obj){
if(obj.hasOwnProperty(item)){
keys.push(obj[item])
}
}
return values;
}
实现Object.entries()
function objectEntries(){
const entries = [];
for(const item in obj){
if(obj hasOwnPrototype(item)){
entries.push([item,obj[item]])
}
}
return entries
}
防抖函数
防抖函数是合并多次执行,比如延时时间为5秒,如果5秒内有函数触发就合并为最后一次执行,如果5秒没有函数触发则执行
function debounce(fn,wait,imadiate){
//第三个参数为是否立即执行
let timer = null;
//为什么返回函数,有利于函数调用及传递参数
return function(){
//下面参数定义及使用apply为了兼容非箭头函数this指向
let args = arguments,context = this;
//每次进来销毁定时器,重新计时,最后一次触发延时执行
if(timer){cleatTimeout(timer)}
if(imadiate&&!timer){
fn.apply(context,args)
}
timer = setTimeout(()=>{
fn.apply(context,args);
//执行成功后清空定时器
clearTimeout(timer)
},wait)
}
}
测试下
function fn(e){
console.log(e,this.a);
}
const obj={a:1,b:debounce(fn,1000,true)}
document.onmousemove = function(e){
obj.b(e)
}
节流
节流函数是每隔固定的时间执行一次函数,比如试图重绘
funtion throttles(fn,interval,imadiate){
let timer = null
//返回值
return function(){
const args = arguments,context = this;
//开关,防止每次进来都创建一个延时函数
//只有执行过后重新创建延时函数
if(!timer){
timer = setTime(()=>{
fn.apply(context,args);
timer = null;
},interval)
}
}
}
浅拷贝和深拷贝
浅拷贝和深拷贝是针对引用类型的,基本类型存放在栈中,而引用对象既存在于栈中也存在于堆中,应用兑现对象的对像名和在堆中的地址存在栈中
浅拷贝是对象的一层拷贝,对象的属性是基本类型则直接复制,如果是对象则拷贝对象的地址
function shallowCopy(obj){
const result = {};
if(Object.prototype.toString.call(obj)!="[object Object]"){
throw new TypeError()
}
for(item in obj){
if(obj.hasOwnProperty(item)){
result[item] = obj[item]
}
}
return result
}
深拷贝
function deepClone(obj){
const result={};
for(item in obj){
if(obj.hasOwnProperty(item)){
if(typeof obj[item] == 'object'){
result[item] = deepClone(obj[item])
}else{
result[item] = obj[item]
}
}
}
return result
}
二叉树的深度优先遍历
递归版
function deepTreeTraveral(tree){
if(!tree) return
deepTreeTraveral(tree.left);
console.log(tree.val)
deepTreeTraveral(tree.right);
}
迭代版
function iteratorTreeTraveral(tree){
let stask = [],current = tree;
//判断栈或当前节点是否为true
while(task.length>0||current){
//左子树有值一直遍历
while(current){
stash.push(current);
current = current.left;
}
//输出值及遍历当前节点的右子树
current = stash.pop();
console.log(current.val);
current = current.right;
}
}
柯里化函数
curry函数是接受部分参数,参数分批接收,并返回一个函数的函数 例如是先add(1)(2)(3)(4)
function add(x){
//在作用于中记录第一次传入的参数
let sum = x;
//接受剩余的参数并计算,
let tmp = function(y){
sum = sum + y;
除第一次外剩余执行时的执行函数
return tmp
}
//可以是任何函数,最后用于获取值
tmp.toString = ()=>sum
//第一次执行返回tmp函数
return tmp
}
数组去重
测试用例
const arr = [6, 6, '6', true, true, 'true','true', NaN, NaN,'NaN', undefined, undefined, null,null];
console.log(arrayUnique(arr));
// [ 6, '6', true, 'true', NaN, 'NaN', undefined, null ]
实现
function unique(arr){
let uniqueArry = [];
uniqueArry = [...new Set(arr)]
//或者
uniqueArry = Arry.from(new Set(arr))
return uniqueArry
}
//原始写法,但是没有以上两种效率快,内存
function uniqueMap(arr){
const uniqueArr = [];
const map = new Map()
arr.forEach(item=>{
if(!map.has(item)){
uniqueArr.push(item);
map.set(item,true)
}
})
return uniqueArr;
}
数组扁平化
//第一种:使用数组自带的方法flat在不知道有多少层嵌套时flat的入参为Infinity
arr = [1, 2, [3, 4, [5, 6, [7, 8, [9, 10]]]]];
arr.flat(Infinity);
//第二种:使用正则表达式
//先使用JSON.stringify把数组转为字符,
//但是不能使用toString(),toString会直接返回一维数组,但是会把数字转为字符串,对arr[number]不友好
JSON.stringify(arr).replace(/[\[|\]]/,'')
//第三种使用stack
const flat = (arr)=>{
const result = [];
//[...arr]防止只有以为数组
const stack = [...arr];
// const current = stack.shift()
while(stack.length>0){
//保证顺序
const current = stack.shift()
if(Array.isArray(current)){
//推入栈中
stack.unshift(...current)
}else{
result.push(current)
}
}
return result
}
// 第四种方法 使用reduce
const reduceFlat=(arr)=>{
//reduce 第一个参数是执行函数,第二个参数是初始值,
//函数中的上一次返回的结果,第二个参数是当前值
return arr.reduce((result,current)=>{
if(Array.isArray(current)){
return [...result,...reduceFlat(current)]
}else{
return [...result,current]
}
},[])
}
函数组合
是函数式编程中的概念,组合函数执行,把函数组合起来使用,通过以下示例来解释
function compose(f,g){
return function(x){
return f(g(x))
}
}
f、g是函数,x是他们之间通过管道传输的值
组合看起来像是在饲养函数。你就是饲养员,选择两个有特点又遭你喜欢的函数,让它们结合,产下一个崭新的函数。
compose是需要自己实现或者使用第三方库
// 不确定参数个数的compose函数
function composePro(){
const args = [].slice.call(arguments);
return args.reduce((acc,curr)=>{
return (...args)=>{
return acc(curr(...args))
}
})
}
let toUpperCase = (x) => x.toUpperCase();
let exclaim = (x) => x + ' ao ao ao!!!';
let nameHead = (x) => 'neverMore ' + x;
console.log('composePro(exclaim, nameHead): ', composePro(exclaim, nameHead));
let associative1 = composePro(toUpperCase, composePro(exclaim, nameHead));
console.log(associative1('hello')) // NEVERMORE HELLO AO AO AO!!!
模板字符串
实现JavaScript中的模板字符串
先上个测试用例
let name = 'More'
let sex = 'man'
let extra = '!!!'
let data = {name, sex, extra}
// ${} 类型的模板字符串
let strWith$ = '${extra} Hello ${name}, you are a ${sex}'
console.log(render1(strWith$)(data))
// !!! Hello More, you are a man
// {{}} 类型的模板字符串
let strWithDoubleParentheses = '{{extra}} Hello {{name}}, you are a {{sex}}'
console.log(render2(strWithDoubleParentheses, data))
// !!! Hello More, you are a man
主要使用到的技术是RegExp,和字符串中的replace方法,这里不再详细赘述RegExp MDN,replace
正则表达式
let name = 'More'
let sex = 'man'
let extra = '!!!'
let data = {name, sex, extra}
// ${} 类型的模板字符串
let strWith$ = '${extra} Hello ${name}, you are a ${sex}' ;
console.log(render1(strWith$)(data))
// !!! Hello More, you are a man
// {{}} 类型的模板字符串
let strWithDoubleParentheses = '{{extra}} Hello {{name}}, you are a {{sex}}'
console.log(render2(strWithDoubleParentheses)(data))
// !!! Hello More, you are a man
匹配${}的正则
reg = /\$\{(\w+)\}/g
//g全局匹配
//特殊字符需要转译
//()匹配的是\$\{和\w之间的值
//补充知识:(\w)和(\w+),第一个正则是\w只匹配单词长度为一个字母的单词,
function render1(str){
return function(data){
return str.replace(/\$\{(\w+)\}/g,(item,key)=>data[key])
}
}
function render2(str){
return function(data){
return str.replace(/\{\{(.*?)\}\}/g,(item,key)=>data[key])
}
}
数组上的filter 使用方法
a = [1,2,3,4,5];
a.filter((item,index,arr)=>item==3)//[3];
console.log(a)//[1,2,3,4,5]
实现
Array.prototype.myFilter = function(callback,thisArg){
// callback回调函数,thisArg回调函数执行时使用的值
if(!Array.isArray(this)){
throw new TypeError('不是数组!');
}
if( typeof callback !='function'){
throw new TypeError('第一个参数应为函数')
}
const result = [];
for(let i = 0; i< this.length;i++){
if(callback.call(thisArg,this[i],i,this)){
result.push(this[i])
}
}
return result;
}
//测试
let arr = [1, 2, 3, 4, 5, 6]
let test1 = arr.myFilter(item => item > 3)
console.log('test1: ', test1);