一些基础问题
call apply bind
fun.call(this指向,参数1,参数2) 函数执行 更改this指向
Function.prototype.mycall=functoion(context){
context=context||window;
context.fn=this;
let args = [...arguments].slice(1);
let result=context.fn(...args);
delete context.fn;
return result;
}
fun.apply(this指向,参数列表) 执行更改完this指向的函数
Function.prototype.myapply=function(context){
context=context||window;
context.fn=this;
let args=arguments[1]||[];
let result=context.fn(...args);
delete context.fn;
return result;
}
fun.bind() 只更改this的指向,函数并不执行,返回的是一个函数
Function.prototype.myBind = function (context, ...outerArgs) {
let that = this;
let res = function (...innerArgs) {
if (this instanceof res) {// new操作符执行时
that.call(this, ...outerArgs, ...innerArgs);
} else {// 普通bind
that.call(context, ...outerArgs, ...innerArgs);
}
};
res.prototype = this.prototype; //!!!
return res;
};
深拷贝 浅拷贝
浅拷贝:直接复制引用
function shallowCopy(obj){
if(typeOf obj !== 'Object') return;
let newObject = obj instanceof Array ? []:{};
for(let key in obj){
if(obj.hasOwnPrototype(key)){
newObject[key]=obj[key]
}
}
return newObject;
}
深拷贝:指完全的拷贝一个对象,即使嵌套了对象,两者也相互分离,修改一个对象的属性,也不会影响另一个。
function deepCopy(obj){
if(typeof obj!='Object') return;
let newObject== obj instanceof Array?[]:{};
for(let key in obj){
if(obj.haeOwnPrototype(key)){
newObject[key]=typeOf(key)=='Object'? deepCopy(obj[key]):obj[key];
}
}
return newObject;
}
instanceof
function myInstanceof(left, right) {
let proto = Object.getPrototypeOf(left), // 获取对象的原型
prototype = right.prototype; // 获取构造函数的 prototype 对象
// 判断构造函数的 prototype 对象是否在对象的原型链上
while (proto) {
if (proto === prototype) return true;
proto = Object.getPrototypeOf(proto);
}
return false;
}
防抖节流
防抖 在事件被触发 n 秒后再执行回调,如果在这 n 秒内事件又被触发,则重新计时。
function debounce(fn, wait) {
var timer = null;
return function () {
var context = this,args = arguments
if (timer) clearTimeout(timer);
timer=setTimeout(function() {
fn.apply(that, args);
timer=null;
}, wait);
};
}
节流 规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效。
function throttle(fn, delay) {
let timer;
return function () {
var context = this,args = arguments
if (timer) return;
timer = setTimeout(function () {
fn.apply(that, args);
timer = null;
}, delay)
}
}
继承
//1原型
function Father(){}
Father.prototype.name="father"
functuon Son(){}
Son.prototype=Object.create(Father.prototype);//!!!!!
son.prototype.constructor=son;
let s1=new son()
//2构造函数
function Father(){}
functuon Son(){
Father.call(this);
}
let s1=new son()
//3原型链
function Father(){}
Father.prototype.name="father"
functuon Son(){}
Son.prototype=new father()
let s1=new son()
//4原型链构造函数组合
function Father(){}
Father.prototype.name="father"
functuon Son(){
Father.call(this);//继承属性
}
Son.prototype = new Father();//继承方法
let s1=new son()
//5寄生
function createP(obj){
var clone=Object(obj);
clone.getName=function(){
console.log("create");
}
return clone;
}
//6组合寄生
function inheritPrototype(subType, superType){
var prototype = object(superType.prototype); // 创建原型对象是超类原型对象的一个实例对象
prototype.constructor = subType; // 弥补因为重写原型而失去的默认的 constructor 属性。
subType.prototype = prototype; // 实现原型继承
}
new
function mynew(Func, ...args) {
// 1.创建一个新对象
const obj = {}
// 2.新对象原型指向构造函数原型对象
obj.__proto__ = Func.prototype
// 3.将构建函数的this指向新对象
let result = Func.apply(obj, args)
// 4.根据返回值判断
return result instanceof Object ? result : obj
}
数组去重
function removeSame(data){
return [...new Set(data)]
}
function removeSame(data){
return data.filter((value,index)=>{
data.indexO[value]==index;
})
}
function removeSame(data){
let unique=[]
data.forEach((element)=>{
if(!unique.includes(element)){
unique.push(element);
}
})
return unique;
}
数组扁平化
function flat(arr){
return arr.toString().split(",").map((item)=>{
return Number(item);
})
}
[].concat(...arr);
function flat(arr){
return arr.reduce((result,item)=>{
return result.concat(Array.isArray(item)?flat(item):item);
},[])
}
pipe
const fn = pipe(addOne, addTwo, addThree, addFour); // 传入pipe的四个函数都是已实现的 fn(1); // 1 + 1 + 2 + 3 + 4 = 11,输出11
//使用reduce实现
const pipeFun=(...fns)=>
fns.reduce((f,g)=>{
return (...args)=>{
return g(f(..args))
};
})
// 同上
const pipe = ...args => x =>
args.reduce(
(outputValue, currentFunction) => currentFunction(outputValue), x )
//ES5
const pipe = function(){
const args = [].slice.apply(arguments);
return function(x) {
return args.reduce((res, cb) => cb(res), x);
}
}
const add5 = (x) => x + 5;
const multiply = (x, y) => x * y;
const add1 = (x) => x + 1;
const multiplyAndAdd5 = pipeFunctions(multiply, add5, add1); // 15
console.log(multiplyAndAdd5(5, 2));
compose
借助Array.prototype.reduce
,这个方法会从左往右迭代,但是我们需要的是从右往左迭代,这个方法是Array.prototype.reduceRight
:
const compose = function(){
// 将接收的参数存到一个数组, args == [multiply, add]
const args = [].slice.apply(arguments);
return function(x) {
return args.reduceRight((res, cb) => cb(res), x);
}
}
const compose1 = (...fns) =>
fns.reduceRight((f, g) => {
return (...args) => {
return g(f(...args));
};
});
柯里化
//这是定长的函数柯里化的通用写法
function curry(fn) {
// 获取原函数的参数长度
const len = fn.length;
let that = this;
// 保存预置参数
const outArgs = Array.prototype.slice.call(arguments, 1);
// 返回一个新函数
return function () {
// 新函数调用时会继续传参
const inArgs = Array.prototype.slice.call(arguments);
const allArgs = [...outArgs, ...inArgs];
if (allArgs.length >= len) {
// 如果参数够了,就执行原函数
return fn.apply(that, allArgs);
} else {
// 否则继续柯里化
return curry.call(that, fn, ...allArgs);
}
};
}
//add 函数是不定长的 所以不可以用以上的通用写法
function add() {
// 第一次执行时,定义一个数组专门用来存储所有的参数
var _args = [].slice.call(arguments);
console.log(_args);
// 在内部声明一个函数,利用闭包的特性保存_args并收集所有的参数值,执行时已经收集所有参数为数组
var adder = function () {
var _adder = function () {
// 执行收集动作,每次传入的参数都累加到原参数
[].push.apply(_args, [].slice.call(arguments));
return _adder;
};
// 利用隐式转换的特性,当最后执行时隐式转换,并计算最终的值返回
_adder.toString = function () {
return _args.reduce(function (a, b) {
return a + b;
});
};
return _adder;
};
return adder(_args);
}
彻底搞懂闭包,柯里化,手写代码,金九银十不再丢分! - 掘金 (juejin.cn)
发布订阅
对象间一对多的依赖关系,当一个对象的状态发生改变的时候,所有依赖于它的对象都将得到状态的改变
- 创建一个对象
- 在该对象上创建一个缓存列表-调度中心
- on方法用来把函数fn加到缓存列表中-订阅者注册事件到调度中心
- emit方法取到arguments里第一个当作event,根据event值去执行对应缓存列表的函数(发布者发布事件到调度中心,调度中心处理代码)
- off方法根据event值取消订阅
//发布订阅模式
let eventEmitter = {
list: {}, //监听队列
//订阅
on(event, fn) {
if (!this.list[event]) {
//没有event的值说明没有订阅过 就创建一个
this.list[event] = [];
}
this.list[event].push(fn);
},
//发布
emit() {
let key = [].shift.call(arguments); //第一个参数是event的值
fns = this.list[key]; //event的对应的队列
if (!fns || fns.length == 0) {
return false;
}
// 遍历 event 值对应的缓存列表,依次执行 fn
fns.forEach((fn) => {
fn.apply(this, arguments);
});
},
//取消订阅
remove(key, fn) {
let fns = this.list[key];
if (!fns) return false;
if (!fn) {
fns && (fns.length = 0);
} else {
fns.forEach((item, i) => {
if (item == fn) {
fns.splice(i, 1);
}
});
}
},
};
观察者
class Subject{
constructor(name){
this.name = name;
this.state = '正常的';
this.observer = [];
}
attach(key,o){
this.observer.push(o);
}
setstate(newstate){
this.state = newstate;
this.observer.forEach(o => o.update(this));
}
}
class Observer{
constructor(name){
this.name = name;
}
update(baby){
console.log(this.name+'被通知了,被观察者状态是'+ baby.state);
}
}
let baby = new Subject("小宝宝");
let father = new Observer("爸爸");
let mother = new Observer("妈妈");
baby.attach(father);
baby.attach(mother);
baby.setState("我饿了");
//忘记从那个博客截的图了 侵权删
一些面试中遇到的
setTimeout实现setInterval
function myInterval(fn, delay) {
let timer = null;
function interval() {
fn();
timer = setTimeout(interval, delay);
}
timer = setTimeout(interval, delay);
}
let cancel = myInterval(() => {
console.log(222);
}, 1000);
cancel();
DOM转JSON
function convertToJson() {
const root = document.getElementsByClassName('root')[0];
const output = new Object();
output.tagName = root.tagName;
output.className = root.className;
output.childs = getChilds(root);
console.log(JSON.stringify(output));
}
function getChilds(node) {
const childs = node.children;
const result = new Array();
if(!childs || childs.length === 0) return result;
for (const child of childs) {
const childOutput = new Object();
childOutput.tagName = child.tagName;
childOutput.className = child.className;
childOutput.childs = getChilds(child);
result.push(childOutput);
}
return result;
}
convertToJson();
数组转对象
- Object.keys():会返回一个给定对象的自身可枚举属性组成的数组,数组中属性名的排列顺序和使用for…in循环遍历该对象时返回的顺序一致 。
- Object.values():返回一个给定对象自身的所有可枚举属性值的数组,值的顺序与使用for…in循环的顺序相同 ( 区别在于 for-in 循环枚举原型链中的属性 )。
- Object.entries():返回一个给定对象自身可枚举属性的键值对数组,其排列与使用for…in循环遍历该对象时返回的顺序一致(区别在于 for-in 循环还会枚举原型链中的属性)。
- Object.fromEntries():把键值对列表转换为一个对象,是Object.entries()的逆操作。
//[{},{}]=>{{},{}}
function change(data){
let obj={};
data.map(a=>{
obj[a.id]=a.value;//对象中的变量需要[]访问
})
return obj;
}
//使用forEach
const arr = [1,2,3,4,5];let obj = {};
arr.forEach((item,index) => { obj[index] = item;})
console.log(obj); //{ 0: 1, 1: 2, 2: 3, 3: 4, 4: 5 }对象转数组
function changeIdToValue(data) {
data.map((item) => {
const { id, name, children } = item; //解构赋值
let _children = children ? changeIdToValue(children) : null;
return { value: id, name: name, children: _children };
});
return data;
}
对象转数组
//得到对象的item
const obj = { a:1 , b: 2, c: 3 };
const arr = Object.entries(obj);
console.log(arr); // [ ['a', 1], ['b', 2], ['c', 3] ]
//Object.keys(obj)
//得到对象的值
const obj = { a:1 , b: 2, c: 3 };
const arr = Object.values(obj);
console.log(arr); // [1, 2, 3];
//
function objToArr() {
const obj = { a: 1, b: 2, c: 3 };
//const arr = Object.entries(obj);
let arr = [];
for (let o of Object.entries(obj)) {
arr.push(o);
}
console.log(arr);
}
objToArr();
数组转树
function transTree(data) {
let result = []
let map = {}
if (!Array.isArray(data)) {//验证data是不是数组类型
return []
}
data.forEach(item => {//建立每个数组元素id和该对象的关系
map[item.id] = item //这里可以理解为浅拷贝,共享引用
})
data.forEach(item => {
let parent = map[item.parentId] //找到data中每一项item的爸爸
if (parent) {//说明元素有爸爸,把元素放在爸爸的children下面
(parent.children || (parent.children = [])).push(item)
} else {//说明元素没有爸爸,是根节点,把节点push到最终结果中
result.push(item) //item是对象的引用
}
})
return result //数组里的对象和data是共享的
}
console.log(JSON.stringify(transTree(data)))
二叉树转数组
function transArr(node) {
let queue= [node]
let data = [] //返回的数组结构
while (queue.length !== 0) { //当队列为空时就跳出循环
let item = queue.shift() //取出队列中第一个元素
data.push({
id: item.id,
parentId: item.parentId,
name: item.name
})
let children = item.children // 取出该节点的孩子
if (children) {
for (let i = 0; i < children.length; i++) {
queue.push(children[i]) //将子节点加入到队列尾部
}
}
}
return data
}
console.log(transArr(node))