function B(name){this.name = name;
};
function A(name,age){//1.将A的原型指向BObject.setPrototypeOf(A,B);//2.用A的实例作为this调用B,得到继承B之后的实例,这一步相当于调用superObject.getPrototypeOf(A).call(this, name)//3.将A原有的属性添加到新实例上this.age = age; //4.返回新实例对象return this;
};
var a = new A('poetry',22);
console.log(a);
思路: 利用
this
的上下文特性。apply
其实就是改一下参数的问题
Function.prototype.myApply = function(context = window, args) {// this-->func context--> obj args--> 传递过来的参数// 在context上加一个唯一值不影响context上的属性let key = Symbol('key')context[key] = this; // context为调用的上下文,this此处为函数,将这个函数作为context的方法// let args = [...arguments].slice(1) //第一个参数为obj所以删除,伪数组转为数组let result = context[key](...args); // 这里和call传参不一样// 清除定义的this 不删除会导致context属性越来越多delete context[key]; // 返回结果return result;
}
// 使用
function f(a,b){console.log(a,b)console.log(this.name)
}
let obj={name:'张三'
}
f.myApply(obj,[1,2]) //arguments[1]
redux-thunk
可以利用redux
中间件让redux
支持异步的action
// 如果 action 是个函数,就调用这个函数
// 如果 action 不是函数,就传给下一个中间件
// 发现 action 是函数就调用
const thunk = ({ dispatch, getState }) => (next) => (action) => {if (typeof action === 'function') {return action(dispatch, getState);}return next(action);
};
export default thunk
String.prototype.padStart
和 String.prototype.padEnd
是ES8
中新增的方法,允许将空字符串或其他字符串添加到原始字符串的开头或结尾。我们先看下使用语法:
String.padStart(targetLength,[padString])
用法:
'x'.padStart(4, 'ab') // 'abax'
'x'.padEnd(5, 'ab') // 'xabab'// 1. 若是输入的目标长度小于字符串原本的长度则返回字符串本身
'xxx'.padStart(2, 's') // 'xxx'// 2. 第二个参数的默认值为 " ",长度是为1的
// 3. 而此参数可能是个不确定长度的字符串,若是要填充的内容达到了目标长度,则将不要的部分截取
'xxx'.padStart(5, 'sss') // ssxxx// 4. 可用来处理日期、金额格式化问题
'12'.padStart(10, 'YYYY-MM-DD') // "YYYY-MM-12"
'09-12'.padStart(10, 'YYYY-MM-DD') // "YYYY-09-12"
polyfill实现:
String.prototype.myPadStart = function (targetLen, padString = " ") {if (!targetLen) {throw new Error('请输入需要填充到的长度');}let originStr = String(this); // 获取到调用的字符串, 因为this原本是String{},所以需要用String转为字符串let originLen = originStr.length; // 调用的字符串原本的长度if (originLen >= targetLen) return originStr; // 若是 原本 > 目标 则返回原本字符串let diffNum = targetLen - originLen; // 10 - 6 // 差值for (let i = 0; i < diffNum; i++) { // 要添加几个成员for (let j = 0; j < padString.length; j++) { // 输入的padString的长度可能不为1if (originStr.length === targetLen) break; // 判断每一次添加之后是否到了目标长度originStr = `${padString[j]}${originStr}`;}if (originStr.length === targetLen) break;}return originStr;
}
console.log('xxx'.myPadStart(16))
console.log('xxx'.padStart(16))
还是比较简单的,而padEnd
的实现和它一样,只需要把第二层for
循环里的${padString[j]}${orignStr}
换下位置就可以了。
语法
array.reduce(function(total, currentValue, currentIndex, arr), initialValue);
/*total: 必需。初始值, 或者计算结束后的返回值。currentValue: 必需。当前元素。currentIndex: 可选。当前元素的索引; arr: 可选。当前元素所属的数组对象。initialValue: 可选。传递给函数的初始值,相当于total的初始值。
*/
reduceRight()
该方法用法与reduce()
其实是相同的,只是遍历的顺序相反,它是从数组的最后一项开始,向前遍历到第一项
1. 数组求和
const arr = [12, 34, 23];
const sum = arr.reduce((total, num) => total + num);// 设定初始值求和
const arr = [12, 34, 23];
const sum = arr.reduce((total, num) => total + num, 10); // 以10为初始值求和// 对象数组求和
var result = [{ subject: 'math', score: 88 },{ subject: 'chinese', score: 95 },{ subject: 'english', score: 80 }
];
const sum = result.reduce((accumulator, cur) => accumulator + cur.score, 0);
const sum = result.reduce((accumulator, cur) => accumulator + cur.score, -10); // 总分扣除10分
2. 数组最大值
const a = [23,123,342,12];
const max = a.reduce((pre,next)=>pre>cur?pre:cur,0); // 342
3. 数组转对象
var streams = [{name: '技术', id: 1}, {name: '设计', id: 2}];
var obj = streams.reduce((accumulator, cur) => {accumulator[cur.id] = cur; return accumulator;}, {});
4. 扁平一个二维数组
var arr = [[1, 2, 8], [3, 4, 9], [5, 6, 10]];
var res = arr.reduce((x, y) => x.concat(y), []);
5. 数组去重
实现的基本原理如下:① 初始化一个空数组
② 将需要去重处理的数组中的第1项在初始化数组中查找,如果找不到(空数组中肯定找不到),就将该项添加到初始化数组中
③ 将需要去重处理的数组中的第2项在初始化数组中查找,如果找不到,就将该项继续添加到初始化数组中
④ ……
⑤ 将需要去重处理的数组中的第n项在初始化数组中查找,如果找不到,就将该项继续添加到初始化数组中
⑥ 将这个初始化数组返回
var newArr = arr.reduce(function (prev, cur) {prev.indexOf(cur) === -1 && prev.push(cur);return prev;
},[]);
6. 对象数组去重
const dedup = (data, getKey = () => { }) => {const dateMap = data.reduce((pre, cur) => {const key = getKey(cur)if (!pre[key]) {pre[key] = cur}return pre}, {})return Object.values(dateMap)
}
7. 求字符串中字母出现的次数
const str = 'sfhjasfjgfasjuwqrqadqeiqsajsdaiwqdaklldflas-cmxzmnha';const res = str.split('').reduce((pre,next)=>{pre[next] ? pre[next]++ : pre[next] = 1return pre
},{})
// 结果
-: 1
a: 8
c: 1
d: 4
e: 1
f: 4
g: 1
h: 2
i: 2
j: 4
k: 1
l: 3
m: 2
n: 1
q: 5
r: 1
s: 6
u: 1
w: 2
x: 1
z: 1
8. compose函数
redux compose
源码实现
function compose(...funs) {if (funs.length === 0) {return arg => arg;}if (funs.length === 1) {return funs[0];}return funs.reduce((a, b) => (...arg) => a(b(...arg)))
}
// 使用 promise来实现 sleep
const sleep = (time) => {return new Promise(resolve => setTimeout(resolve, time))
}sleep(1000).then(() => {// 这里写你的骚操作
})
参考 前端进阶面试题详细解答
new操作符做了这些事:
__proto__
要指向构造函数的原型prototypecall/apply
改变 this 的指向object
类型则作为new
方法的返回值返回,否则返回上述全新对象function myNew(fn, ...args) {// 基于原型链 创建一个新对象let newObj = Object.create(fn.prototype);// 添加属性到新对象上 并获取obj函数的结果let res = fn.apply(newObj, args); // 改变this指向// 如果执行结果有返回值并且是一个对象, 返回执行的结果, 否则, 返回新创建的对象return typeof res === 'object' ? res: newObj;
}
// 用法
function Person(name, age) {this.name = name;this.age = age;
}
Person.prototype.say = function() {console.log(this.age);
};
let p1 = myNew(Person, "poety", 18);
console.log(p1.name);
console.log(p1);
p1.say();
function search(arr, target, start, end) {let targetIndex = -1;let mid = Math.floor((start + end) / 2);if (arr[mid] === target) {targetIndex = mid;return targetIndex;}if (start >= end) {return targetIndex;}if (arr[mid] < target) {return search(arr, target, mid + 1, end);} else {return search(arr, target, start, mid - 1);}
}// const dataArr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
// const position = search(dataArr, 6, 0, dataArr.length - 1);
// if (position !== -1) {
// console.log(`目标元素在数组中的位置:${position}`);
// } else {
// console.log("目标元素不在数组中");
// }
var users = [{id: 1, name: '张三'},{id: 2, name: '张三'},{id: 3, name: '张三'},{id: 4, name: '张三'}
]Array.prototype.myFindIndex = function (callback) {// var callback = function (item, index) { return item.id === 4 }for (var i = 0; i < this.length; i++) {if (callback(this[i], i)) {// 这里返回return i}}
}var ret = users.myFind(function (item, index) {return item.id === 2
})console.log(ret)
利用
标签不受跨域限制的特点,缺点是只能支持
get
请求
script
标签script
标签的src
属性,以问号传递参数,设置好回调函数callback
名称html
文本中res
参数就是获取的数据function jsonp({url,params,callback}) {return new Promise((resolve,reject)=>{let script = document.createElement('script')window[callback] = function (data) {resolve(data)document.body.removeChild(script)}var arr = []for(var key in params) {arr.push(`${key}=${params[key]}`)}script.type = 'text/javascript'script.src = `${url}?callback=${callback}&${arr.join('&')}`document.body.appendChild(script)})
}
// 测试用例
jsonp({url: 'http://suggest.taobao.com/sug',callback: 'getData',params: {q: 'iphone手机',code: 'utf-8'},
}).then(data=>{console.log(data)})
CORS: Access-Control-Allow-Origin:*
postMessage
let url = 'http://www.domain.com/?user=anonymous&id=123&id=456&city=%E5%8C%97%E4%BA%AC&enabled';
parseParam(url)
/* 结果
{ user: 'anonymous',id: [ 123, 456 ], // 重复出现的 key 要组装成数组,能被转成数字的就转成数字类型city: '北京', // 中文需解码enabled: true, // 未指定值得 key 约定为 true
}
*/
function parseParam(url) {const paramsStr = /.+\?(.+)$/.exec(url)[1]; // 将 ? 后面的字符串取出来const paramsArr = paramsStr.split('&'); // 将字符串以 & 分割后存到数组中let paramsObj = {};// 将 params 存到对象中paramsArr.forEach(param => {if (/=/.test(param)) { // 处理有 value 的参数let [key, val] = param.split('='); // 分割 key 和 valueval = decodeURIComponent(val); // 解码val = /^\d+$/.test(val) ? parseFloat(val) : val; // 判断是否转为数字if (paramsObj.hasOwnProperty(key)) { // 如果对象有 key,则添加一个值paramsObj[key] = [].concat(paramsObj[key], val);} else { // 如果对象没有这个 key,创建 key 并设置值paramsObj[key] = val;}} else { // 处理没有 value 的参数paramsObj[param] = true;}})return paramsObj;
}
链表结构
看图理解next层级
// 链表 从头尾删除、增加 性能比较好
// 分为很多类 常用单向链表、双向链表// js模拟链表结构:增删改查// node节点
class Node {constructor(element,next) {this.element = elementthis.next = next}
}class LinkedList {constructor() {this.head = null // 默认应该指向第一个节点this.size = 0 // 通过这个长度可以遍历这个链表}// 增加O(n)add(index,element) {if(arguments.length === 1) {// 向末尾添加element = index // 当前元素等于传递的第一项index = this.size // 索引指向最后一个元素}if(index < 0 || index > this.size) {throw new Error('添加的索引不正常')}if(index === 0) {// 直接找到头部 把头部改掉 性能更好let head = this.headthis.head = new Node(element,head)} else {// 获取当前头指针let current = this.head// 不停遍历 直到找到最后一项 添加的索引是1就找到第0个的next赋值for (let i = 0; i < index-1; i++) { // 找到它的前一个current = current.next}// 让创建的元素指向上一个元素的下一个// 看图理解next层级current.next = new Node(element,current.next) // 让当前元素指向下一个元素的next}this.size++;}// 删除O(n)remove(index) {if(index < 0 || index >= this.size) {throw new Error('删除的索引不正常')}this.size--if(index === 0) {let head = this.headthis.head = this.head.next // 移动指针位置return head // 返回删除的元素}else {let current = this.headfor (let i = 0; i < index-1; i++) { // index-1找到它的前一个current = current.next}let returnVal = current.next // 返回删除的元素// 找到待删除的指针的上一个 current.next.next // 如删除200, 100=>200=>300 找到200的上一个100的next的next为300,把300赋值给100的next即可current.next = current.next.next return returnVal}}// 查找O(n)get(index) {if(index < 0 || index >= this.size) {throw new Error('查找的索引不正常')}let current = this.headfor (let i = 0; i < index; i++) {current = current.next}return current}
}var ll = new LinkedList()ll.add(0,100) // Node { ellement: 100, next: null }
ll.add(0,200) // Node { element: 200, next: Node { element: 100, next: null } }
ll.add(1,500) // Node {element: 200,next: Node { element: 100, next: Node { element: 500, next: null } } }
ll.add(300)
ll.remove(0)console.log(ll.get(2),'get')
console.log(ll.head)module.exports = LinkedList
Array.prototype.myMap = function(callback, context){// 转换类数组var arr = Array.prototype.slice.call(this),//由于是ES5所以就不用...展开符了mappedArr = [], i = 0;for (; i < arr.length; i++ ){// 把当前值、索引、当前数组返回去。调用的时候传到函数参数中 [1,2,3,4].map((curr,index,arr))mappedArr.push(callback.call(context, arr[i], i, this));}return mappedArr;
}
题目描述
编写一个函数来查找字符串数组中的最长公共前缀。
如果不存在公共前缀,返回空字符串 ""。示例 1:输入:strs = ["flower","flow","flight"]
输出:"fl"示例 2:输入:strs = ["dog","racecar","car"]
输出:""
解释:输入不存在公共前缀。
答案
const longestCommonPrefix = function (strs) {const str = strs[0];let index = 0;while (index < str.length) {const strCur = str.slice(0, index + 1);for (let i = 0; i < strs.length; i++) {if (!strs[i] || !strs[i].startsWith(strCur)) {return str.slice(0, index);}}index++;}return str;
};
题目描述:JS 实现一个带并发限制的异步调度器 Scheduler
,保证同时运行的任务最多有两个
addTask(1000,"1");addTask(500,"2");addTask(300,"3");addTask(400,"4");的输出顺序是:2 3 1 4整个的完整执行流程:一开始1、2两个任务开始执行
500ms时,2任务执行完毕,输出2,任务3开始执行
800ms时,3任务执行完毕,输出3,任务4开始执行
1000ms时,1任务执行完毕,输出1,此时只剩下4任务在执行
1200ms时,4任务执行完毕,输出4
实现代码如下:
class Scheduler {constructor(limit) {this.queue = [];this.maxCount = limit;this.runCounts = 0;}add(time, order) {const promiseCreator = () => {return new Promise((resolve, reject) => {setTimeout(() => {console.log(order);resolve();}, time);});};this.queue.push(promiseCreator);}taskStart() {for (let i = 0; i < this.maxCount; i++) {this.request();}}request() {if (!this.queue || !this.queue.length || this.runCounts >= this.maxCount) {return;}this.runCounts++;this.queue.shift()().then(() => {this.runCounts--;this.request();});}
}
const scheduler = new Scheduler(2);
const addTask = (time, order) => {scheduler.add(time, order);
};
addTask(1000, "1");
addTask(500, "2");
addTask(300, "3");
addTask(400, "4");
scheduler.taskStart();
简单实现
function createStore(reducer) {let currentStatelet listeners = []function getState() {return currentState}function dispatch(action) {currentState = reducer(currentState, action)listeners.map(listener => {listener()})return action}function subscribe(cb) {listeners.push(cb)return () => {}}dispatch({type: 'ZZZZZZZZZZ'})return {getState,dispatch,subscribe}
}// 应用实例如下:
function reducer(state = 0, action) {switch (action.type) {case 'ADD':return state + 1case 'MINUS':return state - 1default:return state}
}const store = createStore(reducer)console.log(store);
store.subscribe(() => {console.log('change');
})
console.log(store.getState());
console.log(store.dispatch({type: 'ADD'}));
console.log(store.getState());
2. 迷你版
export const createStore = (reducer,enhancer)=>{if(enhancer) {return enhancer(createStore)(reducer)}let currentState = {}let currentListeners = []const getState = ()=>currentStateconst subscribe = (listener)=>{currentListeners.push(listener)}const dispatch = action=>{currentState = reducer(currentState, action)currentListeners.forEach(v=>v())return action}dispatch({type:'@@INIT'})return {getState,subscribe,dispatch}
}//中间件实现
export applyMiddleWare(...middlewares){return createStore=>...args=>{const store = createStore(...args)let dispatch = store.dispatchconst midApi = {getState:store.getState,dispatch:...args=>dispatch(...args)}const middlewaresChain = middlewares.map(middleware=>middleware(midApi))dispatch = compose(...middlewaresChain)(store.dispatch)return {...store,dispatch}}// fn1(fn2(fn3())) 把函数嵌套依次调用
export function compose(...funcs){if(funcs.length===0){return arg=>arg}if(funs.length===1){return funs[0]}return funcs.reduce((ret,item)=>(...args)=>ret(item(...args)))
}//bindActionCreator实现function bindActionCreator(creator,dispatch){return ...args=>dispatch(creator(...args))
}
function bindActionCreators(creators,didpatch){//let bound = {}//Object.keys(creators).forEach(v=>{// let creator = creator[v]// bound[v] = bindActionCreator(creator,dispatch)//})//return boundreturn Object.keys(creators).reduce((ret,item)=>{ret[item] = bindActionCreator(creators[item],dispatch)return ret},{})
}
第一版 时间复杂度为 O(n^2)
function getTenNum(testArray, n) {let result = [];for (let i = 0; i < n; ++i) {const random = Math.floor(Math.random() * testArray.length);const cur = testArray[random];if (result.includes(cur)) {i--;break;}result.push(cur);}return result;
}
const testArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14];
const resArr = getTenNum(testArray, 10);
第二版 标记法 / 自定义属性法 时间复杂度为 O(n)
function getTenNum(testArray, n) {let hash = {};let result = [];let ranNum = n;while (ranNum > 0) {const ran = Math.floor(Math.random() * testArray.length);if (!hash[ran]) {hash[ran] = true;result.push(ran);ranNum--;}}return result;
}
const testArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14];
const resArr = getTenNum(testArray, 10);
第三版 交换法 时间复杂度为 O(n)
function getTenNum(testArray, n) {const cloneArr = [...testArray];let result = [];for (let i = 0; i < n; i++) {debugger;const ran = Math.floor(Math.random() * (cloneArr.length - i));result.push(cloneArr[ran]);cloneArr[ran] = cloneArr[cloneArr.length - i - 1];}return result;
}
const testArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14];
const resArr = getTenNum(testArray, 14);
值得一提的是操作数组的时候使用交换法 这种思路在算法里面很常见
最终版 边遍历边删除 时间复杂度为 O(n)
function getTenNum(testArray, n) {const cloneArr = [...testArray];let result = [];for (let i = 0; i < n; ++i) {const random = Math.floor(Math.random() * cloneArr.length);const cur = cloneArr[random];result.push(cur);cloneArr.splice(random, 1);}return result;
}
const testArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14];
const resArr = getTenNum(testArray, 14);
Object.create()
方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__
// 模拟 Object.createfunction create(proto) {function F() {}F.prototype = proto;return new F();
}
思路:深度比较两个对象,就是要深度比较对象的每一个元素。=> 递归
null
,直接判断另一个元素是否也为null
keys
数量不同key
function isEqual(obj1, obj2){//其中一个为值类型或nullif(!isObject(obj1) || !isObject(obj2)){return obj1 === obj2;}//判断是否两个参数是同一个变量if(obj1 === obj2){return true;}//判断keys数是否相等const obj1Keys = Object.keys(obj1);const obj2Keys = Object.keys(obj2);if(obj1Keys.length !== obj2Keys.length){return false;}//深度比较每一个keyfor(let key in obj1){if(!isEqual(obj1[key], obj2[key])){return false;}}return true;
}
// Dep module
class Dep {static stack = []static target = nulldeps = nullconstructor() {this.deps = new Set()}depend() {if (Dep.target) {this.deps.add(Dep.target)}}notify() {this.deps.forEach(w => w.update())}static pushTarget(t) {if (this.target) {this.stack.push(this.target)}this.target = t}static popTarget() {this.target = this.stack.pop()}
}// reactive
function reactive(o) {if (o && typeof o === 'object') {Object.keys(o).forEach(k => {defineReactive(o, k, o[k])})}return o
}function defineReactive(obj, k, val) {let dep = new Dep()Object.defineProperty(obj, k, {get() {dep.depend()return val},set(newVal) {val = newValdep.notify()}})if (val && typeof val === 'object') {reactive(val)}
}// watcher
class Watcher {constructor(effect) {this.effect = effectthis.update()}update() {Dep.pushTarget(this)this.value = this.effect()Dep.popTarget()return this.value}
}// 测试代码
const data = reactive({msg: 'aaa'
})new Watcher(() => {console.log('===> effect', data.msg);
})setTimeout(() => {data.msg = 'hello'
}, 1000)