刷题
JS 实现千位分隔符
- 正则实现
function numFormat(num){
var res=num.toString().replace(/\d+/, function(n){ // 先提取整数部分
console.log(`n:${n}`)
return n.replace(/(\d)(?=(\d{3})+$)/g,function(m){
console.log(`m:${m}`)
return m + ",";
});
})
return res;
}
// 输出
n:1234567894532
m:1
m:4
m:7
m:4
- js自带函数toLocaleString()
1234567894532 .toLocaleString()
JS实现一个无限累加的add函数
add(1) //1
add(1)(2) //3
add(1)(2)(3) //6
function add(a) {
function sum(b) { // 使用闭包
a = a + b; // 累加
return sum;
}
sum.toString = function() { // 重写toString()方法
return a;
}
return sum; // 返回一个函数
}
使下面代码正常运行
const a = [1,2,3,4,5]
a.multiply()
console.log(a) // [1,2,3,4,5,1,4,9,16,25]
Array.prototype.multiply = function() {
return this.concat(this.map(function(v){
return v * v
}))
}
1.toString()报错
在JS中.点操作符意味着调用Object的属性或者这是一个浮点数。当.跟在一个数字后面就意味着这个数字是一个浮点数。js的解释器把数字后的"."偷走了(作为前面数字的小数点)
1.toString() // Uncaught SyntaxError: Invalid or unexpected token
1..toString()
1 .toString()
发布订阅模式和观察者模式
发布订阅模式
EventEmitter 的核心就是事件触发与事件监听器功能的封装。
主要是用emit发事件,用on监听事件,还有removeListener销毁事件监听者
- 发布订阅模式中,对于发布者Publisher和订阅者Subscriber没有特殊的约束
- 松散耦合,灵活度高
- 易理解,可类比于DOM事件中的dispatchEvent和addEventListener
- 当事件类型越来越多时,难以维护,需要考虑事件命名的规范,也要防范数据流混乱。
class EventEmitter {
constructor() {
// 维护事件及监听者
this.listeners = {}
}
/**
* 注册事件监听者
* @param {String} type 事件类型
* @param {Function} cb 回调函数
*/
on(type, cb) {
if (!this.listeners[type]) {
this.listeners[type] = []
}
this.listeners[type].push(cb)
}
/**
* 发布事件
* @param {String} type 事件类型
* @param {...any} args 参数列表,把emit传递的参数赋给回调函数
*/
emit(type, ...args) {
if (this.listeners[type]) {
this.listeners[type].forEach(cb => {
cb(...args)
})
}
}
/**
* 移除某个事件的一个监听者
* @param {String} type 事件类型
* @param {Function} cb 回调函数
*/
removeListener(type, cb) {
if (this.listeners[type]) {
const targetIndex = this.listeners[type].findIndex(item => item === cb)
if (targetIndex !== -1) {
this.listeners[type].splice(targetIndex, 1)
}
if (this.listeners[type].length === 0) {
delete this.listeners[type]
}
}
}
/**
* 移除某个事件的所有监听者
* @param {String} type 事件类型
*/
removeAllListeners(type) {
if (this.listeners[type]) {
delete this.listeners[type]
}
}
}
观察者模式
在观察者模式中,只有两个主体,分别是目标对象Subject,观察者Observer。
- 观察者需Observer要实现update方法,供目标对象调用。update方法中可以执行自定义的业务代码。
- Subject需要维护自身的观察者数组observerList,当自身发生变化时,通过调用自身的notify方法,依次通知每一个观察者执行update方法。
// 观察者
class Observer {
/**
* 构造器
* @param {Function} cb 回调函数,收到目标对象通知时执行
*/
constructor(cb){
if (typeof cb === 'function') {
this.cb = cb
} else {
throw new Error('Observer构造器必须传入函数类型!')
}
}
/**
* 被目标对象通知时执行
*/
update() {
this.cb()
}
}
// 目标对象
class Subject {
constructor() {
// 维护观察者列表
this.observerList = []
}
/**
* 添加一个观察者
* @param {Observer} observer Observer实例
*/
addObserver(observer) {
this.observerList.push(observer)
}
/**
* 通知所有的观察者
*/
notify() {
this.observerList.forEach(observer => {
observer.update()
})
}
}
实现 Storage
使得该对象为单例,并对localStorage进行封装设置值setItem(key,value)和getItem(key) 单例模式: 保证一个类只有一个实例,并提供一个访问它的全局访问点
var instance = null;
class Storage {
static getInstance() {
if (!instance) {
instance = new Storage();
}
return instance;
}
setItem = (key, value) => localStorage.setItem(key, value)
getItem = key => localStorage.getItem(key)
}
js 一些方法的实现
Object
myIs
Object.myIs = function(x, y) {
if (x === y) {
// +0 != -0
return x !== 0 || 1 / x === 1 / y
}
else {
// NaN == NaN
return x !== x && y !== y
}
}
myCreate
Object.myCreate = function(proto, properties) {
if (typeof proto != 'object' && typeof proto != 'function') {
throw new Error('Object prototype may only be an Object: ' + proto)
}
function F () {}
F.prototype = proto
return new F()
}
call/apply/bind
call/apply
1. 实现: 改变 this 的指向、执行函数
function.call(thisArg, arg1, arg2, ...)
func.apply(thisArg, [argsArray])
Function.prototype.myCall = function (context, ...args) {
context = context || window
context.fn = this
let result = context.fn(...args)
delete context.fn
return result
}
Function.prototype.myApply = function (context, args) {
context = context || window
context.fn = this
let result
if (args) {
result = context.fn(...args)
} else {
result = context.fn()
}
delete context.fn
return result
}
2. 测试:
var value = 2
let obj = {
value: 1
}
function bar(name, age) {
return {
value: this.value,
name: name,
age: age
}
}
console.log(bar.myCall(null))
console.log(bar.myCall(obj, 'kevin', 18))
console.log(bar.myApply(null))
console.log(bar.myApply(obj, ['kevin', 18]))
bind
1. 实现: 返回一个函数、可以传入参数、构造函数
Function.prototype.myBind = function(context, ...args) {
if (typeof this !== "function") {
throw new Error("Function.prototype.bind - what is trying to be bound is not callable")
}
let self = this
let fBound = function () {
// 当作为构造函数时,this 指向实例,此时结果为 true,将绑定函数的 this 指向该实例
// 当作为普通函数时,this 指向 window,此时结果为 false,将绑定函数的 this 指向 context
let _this = this instanceof fBound ? this : context
return self.apply(_this, [...args, ...arguments])
}
// 修改返回函数的 prototype 为绑定函数的 prototype,实例继承绑定函数的原型中的值
fBound.prototype = Object.create(this.prototype)
return fBound
}
2. 测试:
var value = 2 // var 声明的变量被挂到window上
let foo = {
value: 1,
bar: bar.myBind(null) // bind 调用 apply,apply 会兼容 this 执向 context||window
};
function bar() {
console.log(this.value);
}
console.log('======')
foo.bar()
let foo2 = {
value: 1
};
function bar2(name, age) {
this.habit = 'shopping';
console.log(this.value);
console.log(this.friend);
console.log(name);
console.log(age);
}
bar2.prototype.friend = 'kevin';
let bindFoo = bar2.myBind(foo, 'daisy');
console.log('=====')
bindFoo(18)
console.log('=====')
let obj = new bindFoo('18')
数组排序
冒泡排序
依次比较、交换相邻的元素大小 =》稳定
- 比较相邻的元素,如果第一个比第二个大,就交换它们两个
- 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数
- 针对所有的元素重复以上的步骤,除了最后一个
- 重复步骤 1~3,直到排序完成
function bubbleSort(array) {
for (let i = 0; i < array.length -1; i++) {
for(let j = 1; j < array.length -1 - i; j++) {
if (array[j] > array[j + 1]) {
let temp = array[j + 1]
array[j + 1] = array[i]
array[j] = temp
}
}
}
return array
}
选择排序
选择排序: 每一次循环遍历寻找最小的数 =》不稳定
- 找到数组最小的元素,将它和数组红第一个元素交换位置
- 在剩下的元素中找到最小的元素,将它与数组的第二个元素交换位置
- 往复如此,直到将整个数组排序
function selectSort(array) {
for (let i = 0; i < array.length; i++) {
let tempIndex = i
for (let j = i + 1; j < array.length; j ++) {
if (array[j] < array[tempIndex]) {
tempIndex = j
}
}
if (tempIndex !== i) {
let temp = array[tempIndex]
array[tempIndex] = array[i]
array[i] = temp
}
}
return array
}
插入排序
在有序的数组中插入 =》稳定
- 从第一个元素开始,该元素可以认为已经被排序
- 取出下一个元素,在已经排序的元素序列中从后向前扫描
- 如果该元素(已排序)大于新元素,将该元素移到下一位置
- 直到找到已排序的元素小于或者等于新元素的位置
- 将新元素插入到该位置
- 重复步骤 2~5
function insertSort(array) {
for(let i = 1; i ++; i < array.length) {
let current = array[i]
let preIndex = i - 1
while(preIndex >= 0 && array[preIndex] > current) {
array[preIndex + 1] = array[preIndex]
preIndex --
}
array[preIndex + 1] = current
}
return array
}
快速排序
快速排序: =》不稳定
- 在数据集之中,选择一个元素作为"基准"(pivot)。
- 所有小于"基准"的元素,都移到"基准"的左边;所有大于"基准"的元素,都移到"基准"的右边。
- 对"基准"左边和右边的两个子集,不断重复第一步和第二步,直到所有子集只剩下一个元素为止
function binarySearch(array, val, low, heigh) {
if (low > heigh) {
return -1
}
let m = Math.floor((low + heigh) / 2)
if (array[m] == val) {
return m
}
else if (array[m] < val) {
low = m -1
return binarySearch(array, val, low, heigh)
}
else {
heigh = m + 1
return binarySearch(array, val, low, heigh)
}
}
数组去重
indexOf 底层使用的是 === 进行判断,所以使用 indexOf 查找不到 NaN 元素
Set可以去重NaN类型, Set内部认为尽管 NaN === NaN 为 false,但是这两个元素是重复的
获取 object 的 keys 通过 for ... in 或者 object.keys
时间复杂度:O(1)没有循环、O(n)一层for循环、O(n*n)双重for循环
双重 for 循环
对象和 NaN 不去重
function distinct1(arr) {
for (let i = 0, len = arr.length; i < len; i++) {
for (let j = i + 1; j < len; j ++) {
if (arr[i] == arr[j]) {
arr.splice(j, 1)
// splice 会改变数组长度,所以要将数组长度 len 和下标 j 减一
len --
j --
}
}
}
return arr
}
Array.filter + indexOf
对象不去重 NaN 会被忽略掉
function distinct2(arr) {
return arr.filter((item, index) => {
return arr.indexOf(item) == index
})
}
Array.sort + 一遍冒泡排序
对象和 NaN 不去重 数字 1 也不去重
function distinct3(arr) {
let res = []
let sortedArray = arr.sort()
for (let i = 0, len = sortedArray.length; i < len; i++) {
// // 如果是第一个元素或者相邻的元素不相同
if (!i || seen != sortedArray[i]) {
res.push(sortedArray[i])
}
seen = sortedArray[i]
}
return res
}
Object 键值对
全部去重
function distinct4(arr) {
var obj = {}
return arr.filter((item, index, array) => {
// 123 -> 'number123' 与 '123' -> 'string123'
return obj.hasOwnProperty(typeof item + item) ? false : (obj[typeof item + item] = true)
})
}
ES6 set
对象不去重 NaN 去重
function distinct5(arr) {
return Array.from(new Set(arr))
}
数组出现次数最多的数
以空间换时间
定义数组count,对arr中元素出现的次数进行计数,count中最大元素对应的下标,即出现次数最多的元素值
function SearchMuchArray(arr) {
let count = [0,0,0,0,0,0,0,0,0,0]
for(let i = 0, len = arr.length; i < len; i++) {
count[arr[i]]++
}
let maxVal = 0
let maxCount = 0
for(let i = 0, len = count.length; i < len; i++) {
if(count[i] > maxCount) {
maxVal = i
maxCount = count[i]
}
}
return {
maxVal,
maxCount
}
}
使用Map
每个Entry的key存放数组中的数字,value存放该数字出现的次数。最大的value对应的key即出现次数最多的那个数
function SearchMuchMap(arr) {
let myMap = new Map()
for(let i = 0, len = arr.length; i < len; i++) {
let val = arr[i]
if (myMap.has(val)) {
myMap.set(val, myMap.get(val) + 1)
}
else {
myMap.set(val, 1)
}
}
let maxVal = 0
let maxCount = 0
for(let item of myMap) {
if(item[1] > maxCount) {
maxVal = item[0]
maxCount = item[1]
}
}
return {
maxVal,
maxCount
}
}
查找
二分查找
function binarySearch(array, val) {
let low = 0
let heigh = array.length
while (low <= heigh) {
let m = Math.floor((heigh + low) / 2)
if (array[m] == val) {
return m
}
else if (array[m] < val) {
low = m -1
}
else {
heigh = m + 1
}
}
return -1
}
二叉树
获取从根节点到所有叶子节点的路径
// 树的节点个数
function getNodeNum(root) {
if (!root)
return 0
return getNodeNum(root.left) + getNodeNum(root.right) + 1
}
// 树的叶子节点个数
function getLeftNodeNum(root) {
if (!root.left && !root.right)
return 1
return getNodeNum(root.left) + getNodeNum(root.right)
}
function getPathSum(root){
if (!root)
return 0
let value = 0
let list = []
list.push(root)
while(list.length > 0) {
let node = list.pop()
value += getNodeNum(node) * node.value
if(node.left)
list.push(node.left)
if(node.right)
list.push(node.right)
}
return value
}