class Subject {
constructor () {
this.state = 0
this.observe = []
}
getState () {
return this.state
}
setState (val) {
this.state = val
this.observe.forEach((item) => {
item.update()
})
}
attach(ob) {
this.observe.push(ob)
}
}
class Observe {
constructor (name, sub) {
this.name = name
this.sub = sub
this.sub.attach(this)
}
update () {
console.log(`${this.name} update, state: ${this.sub.getState()}`)
}
}
// 工厂模式
class Product {
constructor (name) {
this.name = name
}
getName () {
console.log(this.name)
}
init () {
console.log('init')
}
}
class Factory {
create (name) {
return new Product(name)
}
}
class LoginForm {
constructor () {
this.state = 'hide'
}
show () {
if (this.state === 'show') {
console.log('已经show')
}
this.state = 'show'
console.log('登录框show成功')
}
hide () {
if (this.state === 'hide') {
console.log('已经hide')
}
this.state = 'hide'
console.log('登录框hide成功')
}
}
LoginForm.getInstance = (function () {
let instance = null
return function () {
if (!instance) {
instance = new LoginForm()
}
return instance
}
})()
LazyMan(‘Tony’);
// Hi I am TonyLazyMan(‘Tony’).sleep(10).eat(‘lunch’);
// Hi I am Tony
// 等待了10秒…
// I am eating lunch
LazyMan(‘Tony’).eat(‘lunch’).sleep(10).eat(‘dinner’);
// Hi I am Tony
// I am eating lunch
// 等待了10秒…
// I am eating diner
LazyMan(‘Tony’).eat(‘lunch’).eat(‘dinner’).sleepFirst(5).sleep(10).eat(‘junk food’);
// Hi I am Tony
// 等待了5秒…
// I am eating lunch
// I am eating dinner
// 等待了10秒…
// I am eating junk food
考察点:运用发布订阅者模式,js事件执行机制
function lazyMan(name) {
// 任务清单队列
this.taskList = []
this.name = name
console.log(`Hi I am ${name}`)
setTimeout(() => this.next())
}
lazyMan.prototype = {
// 订阅方法
eat (food) {
const fn = () => {
console.log(`i am eating ${food}`);
this.next()
}
this.taskList.push(fn)
return this
},
sleepFirst (time) {
const fn = () => {
setTimeout(() => {
console.log(`等待了${time}秒`);
this.next()
}, time*1000)
}
this.taskList.unshift(fn)
return this
},
sleep (time) {
const fn = () => {
setTimeout(() => {
console.log(`等待了${time}秒`);
this.next()
}, time*1000)
}
this.taskList.push(fn)
return this
},
// 事件发布
next () {
const fn = this.taskList.shift()
fn && fn()
}
}
function LazyMan (name) {
return new lazyMan(name)
}
LazyMan('Tony')
LazyMan('Tony').sleep(10).eat('lunch')
LazyMan('Tony').eat('lunch').sleep(10).eat('dinner')
LazyMan('Tony').eat('lunch').eat('dinner').sleepFirst(5).sleep(10).eat('junk food')
就是事件的触发机制,发布订阅者的一个实现,和上题思路一样
function eventEmitter () {
// 定义事件池
this.eventpool = {}
// 事件绑定
this.on = function (event, callback) {
this.eventpool[event] ? this.eventpool[event].push(callback) : this.eventpool[event]
}
// 事件分发
this.emit = function (event, ...args) {
this.eventpool[event] && this.eventpool[event].forEach((cb) => cb(...args))
}
// 事件解绑
this.off (event) {
if (this.eventpool[event]) {
delete this.eventpool[event]
}
}
// 事件只绑定执行一次
this.once(event, callback) {
this.on(event, (...args) => {
callback(...args)
this.off(event)
})
}
}
考察点:事件触发与事件监听器功能的封装
以上两个手撕题涉及的
发布 + 订阅:
DOM 的事件机制就是发布订阅模式最常见的实现,这大概是前端最常用的编程模型了,监听某事件,当该事件发生时,监听该事件的监听函数被调用。
————————————————————————————————————
发布订阅模式,阮一峰在《Javascript 异步编程的 4 种方法》,中:我们假定,存在一个"信号中心",某个任务执行完成,就向信号中心"发布"(publish)一个信号,其他任务可以向信号中心"订阅"(subscribe)这个信号,从而知道什么时候自己可以开始执行。这就叫做"发布/订阅模式"(publish-subscribe
pattern),又称"观察者模式"(observer pattern)。
// call的实现(实现call)
Function.prototype.call = function (context) {
context = context ? Object(context) : window
context.fn = this
let args = [...arguments].slice(1)
let res = context.fn(...args)
delete context.fn
return res
}
// apply的实现
Function.prototype.apply = function (context, arr) {
context = context ? Object(context) : window
context.fn = this
let res
if (!arr) {
res = context.fn()
} else {
res = context.fn(...arr)
}
delete context.fn
return res
}
// 函数实现
function bind(fn, context) {
let args = Array.prototype.slice.call(arguments, 2)
return function () {
return fn.apply(context, args.concat(Array.prototype.slice.call(arguments)))
}
}
// 原型链修改
Function.prototype.bind = function (context) {
let that = this
let args = Array.prototype.slice.call(arguments, 1)
return function () {
return that.apply(context, args.concat(Array.prototype.slice.call(arguments)))
}
}
// 更为完整的方法
Function.prototype.bind2 = function (context) {
if (typeof this !== "function") {
throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
}
var self = this;
var args = Array.prototype.slice.call(arguments, 1);
var fNOP = function () {};
var fBound = function () {
var bindArgs = Array.prototype.slice.call(arguments);
return self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs));
}
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
return fBound;
}
// 不使用其他js函数实现map
Array.prototype.map = function (fn) {
let arr = []
for (let i = 0; i < this.length; i++) {
arr. push(fn(this[i], i, this))
}
return arr
}
// 使用reduce实现map
Array.prototype.map = function (fn) {
return this.reduce((arr, cur, index) => {
arr.push(fn(cur, index, this))
return arr
}, [])
}
// 实现reduce
Array.prototype.reduce = function (fn, initVal) {
let res = initVal ? initVal : 0
for (let i = initVal ? 1 : 0; i < this.length; i++) {
res = fn(res, this[i], i, this)
}
return res
}
Array.prototype.filter = function (fn) {
let arr = []
for (let i = 0; i < this.length; i++) {
if (fn(this[i], i, this)) {
arr.push(this[i])
}
}
return arr
}
// 实现原生splice
Array.prototype.splice = function (again, num) {
let frontArr = []
let afterArr = []
let args = Array.prototype.slice.call(arguments, 2)
for (let i = 0; i < again; i++) {
frontArr[i] = this[i]
}
if (args.length) {
for (let i = 0; i < args.length; i++) {
frontArr[again + i] = this[i]
}
}
for (let i = again + num, j = 0; i < this.length; i++, j++) {
afterArr[j] = this[i]
}
let deleteArr = []
for (let i = 0; i < num; i++) {
deleteArr[i] = this[again + i]
}
let a = frontArr.concat(afterArr)
this.length = a.length
for (let i = 0; i < a.length; i++) {
this[i] = a[i]
}
return deleteArr
}
Array.prototype.push = function () {
let args = arguments
for (let i = 0; i < args.length; i++) {
this[this.length] = args[i]
}
return this.length
}
Array.prototype.pop = function () {
if(this.length === 0) return
let val = this[this.length - 1]
this.length -= 1
return val
}
Array.prototype.unshift = function () {
let args = [...arguments]
let len = args.length
for (let i = this.length - 1; i >= 0; i--) {
this[i + len] = this[i]
}
for (let i = 0; i < len; i++) {
this[i] = args[i]
}
return this.length
}
Array.prototype.shift = function () {
let removeVal = this[0]
for (let i = 0; i < this.length; i++) {
if (i !== this.length - 1) {
this[i] = this[i + 1]
}
}
this.length -= 1
return removeVal
}
考察点:
前端路由的两种实现方式
1、location.hash+hashchange事件。
2、history.pushState()+popstate事件。
// 这里用hash的路由方法实现
function Router() {
this.routes = {}
this.curUrl = ''
this.init()
}
Router.prototype.route = function (path, cb) {
this.routes[path] = cb || function () {}
}
Router.prototype.refresh = function () {
this.curUrl = location.hash.slice(1) || '/'
this.routes[this.curUrl] && this.routes[this.curUrl]()
}
Router.prototype.init = function () {
window.addEventListener('load', this.refresh.bind(this))
window.addEventListener('hashchange', this.refresh.bind(this))
}
// 用个例子试用一下
var router = new Router()
router.route('/', function () {
var body = document.getElementById('page-type')
body.innerHTML = '首页耶耶耶'
})
router.route('/news', function () {
var body = document.getElementById('page-type')
body.innerHTML = '新闻耶耶耶'
})
window.onload = function () {
let ul1 = document.getElementById('ul1')
let li = ul1.getElementsByTagName('li')
// 方法一(挨个li标签加方法)---不推荐
// for (let i = 0; i < li.length; i++) {
// li[i].addEventListener('click', function (e) {
// alert(li[i].innerHTML)
// })
// }
// 方法二:(事件委托)
ul1.onclick = function (e) {
var ev = e || window.event
if (ev.target.nodeName.toLowerCase() === 'li') {
alert(ev.target.innerHTML)
}
}
}
function mySetInterval (cb, time) {
const fn = () => {
cb()
setTimeout(() => {
fn()
}, time)
}
setTimeout(fn, time)
}
// 函数柯里化
// 方式一:
// function curry (fn) {
// var args = Array.prototype.slice.call(arguments, 1)
// return function () {
// return fn.apply(null, args.concat(Array.prototype.slice.call(arguments)))
// }
// }
// 方式二:
let curry = (fn) =>
judge = (...args) =>
args.length >= fn.length ?
fn(...args): (...arg) => judge(...args, ...arg)
// 例子
function add (a, b, c) {
return a + b + c
}
var res = curry(add(1))
var res1 = res(2)
console.log(res1(3))
考察点:
主要是要实现 既返回函数又返回计算结果
所以重写函数本身的toString()方法,在函数转换时它会自动调用。
先来简单了解下这两个方法:
Object.prototype.valueOf():
用 MDN 的话来说,valueOf() 方法返回指定对象的原始值。JavaScript 调用 valueOf() 方法用来把对象转换成原始类型的值(数值、字符串和布尔值)。但是我们很少需要自己调用此函数,valueOf 方法一般都会被 JavaScript 自动调用。
Object.prototype.toString():
toString() 方法返回一个表示该对象的字符串。每个对象都有一个 toString() 方法,当对象被表示为文本值时或者当以期望字符串的方式引用对象时,该方法被自动调用。
这里先记住,valueOf() 和 toString() 在特定的场合下会自行调用。
原始类型:
好,铺垫一下,先了解下 javascript 的几种原始类型,除去 Object 和 Symbol,有如下几种原始类型:Number
String
Boolean
Undefined
Null
function sum() {
let args = [].slice.call(arguments);
let fn = function () {
let fn_args = [].slice.call(arguments)
return sum.apply(null, args.concat(fn_args))
}
fn.toString = function () {
return args.reduce((a, b) => a + b)
}
return fn
}
// 1:原型链继承
function Cat1 () {
}
Cat1.prototype = new Animal()
// 2:构造函数继承
function Cat2 (name) {
Animal.call(this)
this.name = name || 'Cat'
}
// 3: 实例继承
function Cat3 (name) {
let instance = new Animal()
instance.name = name || 'Cat'
return instance
}
// 4:拷贝继承
function Cat4 (name) {
var animal = new Animal()
for (let p in animal) {
Cat4.prototype[p] = animal[p]
}
Cat4.prototype.name = name || 'Cat'
}
// 5: 组合继承(常用)
function Cat5 () {
Animal.call(this)
this.name = name || 'Cat'
}
Cat5.prototype = new Animal()
Cat5.prototype.constructor = Cat5
// 6:寄生组合继承
function Cat5 () {
Animal.call(this)
this.name = name || 'Cat'
}
(function () {
var Super = function () {}
Super.prototype = Animal.prototype
Cat5.prototype = new Super()
})()
Cat5.prototype.constructor = Cat5
考察点:
(new的过程:
• 创建一个空对象,将它的引用赋给 this,继承函数的原型。
• 通过 this 将属性和方法添加至这个对象
• 最后返回 this 指向的新对象,也就是实例(如果没有手动返回其他的对象)
// new的过程
function parent (name, age) {
this.name = name
this.age = age
}
parent.prototype.getName = function () {
console.log(this.name);
}
let newObj = function (par) {
let args = Array.prototype.slice.call(arguments, 1)
let child = Object.create(par.prototype)
par.apply(child, args)
return child
}
let children = newObj(parent, 'haha', 22)
children.getName()
console.log(children.hasOwnProperty('say'));
用函数实现new:
function creat () {
// 获得父类构造函数。(也就是传入的arguments中的第一个参数)
var Con = [].shift.call(arguments)
// 创建一个空对象并将其链接到原型,使得可以访问到构造函数的原型属性
var obj = Object.create(Con.prototype)
// 绑定this实现继承,obj可以访问构造函数中的属性
var result = Con.apply(obj, arguments)
// 优先返回构造函数返回的对象
return result instanceof Object ? result : obj
}
考察点:
promise的原理及各种状态
function myPromise (fn) {
let val = null, succcallbacks = [], failcallbacks = []
let data = null, reason = null, status = 'pending'
this.then = function (fulfilled, rejected) {
if (status === 'pending') {
succcallbacks.push(fulfilled)
failcallbacks.push(rejected)
return this
} else if (status === 'resolve') {
fulfilled(data)
} else {
rejected(reason)
}
}
function resolve (val) {
setTimeout(() => {
data = val
status = 'fulfilled'
succcallbacks.forEach((callback) => {
callback(val)
})
}, 0)
}
function reject (val) {
setTimeout(() => {
reason = val
status = 'rejected'
failcallbacks.forEach((callback) => {
callback(val)
})
}, 0)
}
fn(resolve, reject)
}
// 测试
function test(num) {
return new myPromise((resolve, reject) => {
setTimeout(() => {
resolve(num)
}, 1000)
})
}
// promise重传
function tryPromise (fn, count) {
fn().then((result) => {
return result
}, (err) => {
if (err) {
if (count) {
tryPromise(fn, count--)
}
}
})
}
// 使用promise封装ajax函数
var xmlhttp = new XMLHttpRequest()
function ajax (type, api, data) {
return new Promise((resolve, reject) => {
xmlhttp.onreadystatechange = () => {
if (xmlhttp.readyState === 4) {
if (xmlhttp.status === 200) {
resolve(xmlhttp.responseText)
} else {
reject(xmlhttp.status)
}
}
}
xmlhttp.open(type, api, data)
xmlhttp.send
})
}
function deepCopy (obj) {
if (!obj instanceof Object) {
throw new Error('Not a Object')
}
var newObj = Array.isArray(obj) ? [] : {}
for (let key in obj) {
newObj[key] = obj[key] instanceof Object ? deepCopy(obj[key]) : obj[key]
}
return newObj
}
function instance_of(L, R){ // 表示表达式左边R和右边L
R = R.prototype // 取 R 的显示原型
L = L.__proto__ // 取 L 的隐式原型
while (true) {
if (L === null) return false // Object.prototype.__proto__ === null
if (R === L) return true // 这里重点:当 O 严格等于 L 时,返回 true(即实例在原型链上)
L = L.__proto__
}
}
// 防抖
function debounce (fn) {
let instance = null
return function () {
clearTimeout(instance)
instance = setTimeout(fn, 2000)
}
}
// 节流
function throttle (fn, wait, maxTime) {
let instance = null
let startTime = new Date()
return function () {
let endTime = new Date()
let args = arguments
let that = this
clearTimeout(instance)
if (endTime - startTime >= maxTime) {
fn.apply(that, args)
startTime = endTime
} else {
instance = setTimeout(fn, wait)
}
}
}
// 数组扁平化
let flatArr123 = [1, 3, [2, [5, 6]], [4], [[6, 7, [8]]]]
// 1:
flatArr123.flat(Infinity)
// 2:
function flatten2(arr) {
return arr.reduce((result, item) => {
return result.concat(Array.isArray(item) ? flatten2(item) : item)
}, [])
}
// 3:
function flatten3(arr) {
return arr.toString().split(',').map(x => +x)
}
// 4:
function flatten4(arr) {
while (arr.some(item => Array.isArray(item))) {
arr = [].concat(...arr)
}
return arr
}
// 5:
function flatten5(arr) {
var res = []
arr.map(item => {
if (Array.isArray(item)) {
res = res.concat(flatten5(item))
} else {
res.push(item)
}
})
return res
}
function buildObj(dataObjStr) {
var array = dataObjStr.split(".")
var result = {}
var temp = result
for (var i = 0; i < array.length; i++) {
temp = temp[array[i]] = {}
}
return result
}
递归:
// 前序遍历
function preorderTraversal(root, arr = []) {
if (root === null) return
arr.push(root.val)
preorderTraversal(root.left, arr)
preorderTraversal(root.right, arr)
return arr
}
// 中序遍历
function inorderTraversal(root, arr = []) {
if (root === null) return
inorderTraversal(root.left, arr)
arr.push(root.val)
inorderTraversal(root.right, arr)
return arr
}
// 后序遍历
function postorderTraversal(root, arr = []) {
if (root === null) return
postorderTraversal(root.left, arr)
postorderTraversal(root.right, arr)
arr.push(root.val)
return arr
}
非递归:
// 前序遍历
function preorderTraversal(root) {
var stack = []
var res = []
var p = root
if (root === null) return []
while (p || stack.length) {
while (p) {
stack.push(p)
res.push(p.val)
p = p.left
}
p = stack.pop()
p = p.right
}
return res
}
// 中序遍历
function inorderTraversal(root) {
var stack = []
var res = []
var p = root
if (root === null) return []
while (p || stack.length) {
while (p) {
stack.push(p)
p = p.left
}
p = stack.pop()
res.push(p.val)
p = p.right
}
return res
}
// 后序遍历
function postorderTraversal(root, arr = []) {
var stack = []
var res = []
var p = root
while (p || stack.length) {
if (p) {
stack.push(p)
res.unshift(p.val)
p = p.right
} else {
var node = stack.pop()
p = node.left
}
}
return res
}
层序遍历:
function isCompleteTree (tree) {
let queue = []
queue.push(tree)
while (queue.length) {
let p = queue.shift()
if (p) {
queue.push(p.left)
queue.push(p.right)
} else {
while (queue.length) {
if (queue.shift()) {
return false
}
}
}
}
return true
}
建立完全二叉树测试:
// 这里用个简单的递归方法简历完全二叉树来测试
// 树的节点
function bitTreeNode(val) {
this.val = val
this.right = null
this.left = null
}
// 递归建立
function creatTree(n) {
if (n === 1) return new bitTreeNode(1)
let root = new bitTreeNode(n)
root.left = creatTree(n - 1)
root.right = creatTree(n - 1)
return root
}
var tree = creatTree(5)
console.log(tree)
知识点:
主要实现对象的嵌套调用情况,如 a.b.c.d.e,因为这么写很容易抛出异常。(正常写法得 a && a.b && a.b.c && a.b.c.d && a.b.c.d.e)
function _get (source, path, defaultValue = undefined) {
const paths = path.replace(/\[(\d+)\]/g, '.$1').split('.')
let res = source
for (let val of paths) {
res = Object(res)[val]
if (res === undefined) {
return defaultValue
}
}
return res
}
function urlToObject () {
let url = window.location.href
url = url.indexOf('?') === -1 ? 'https://www.nowcoder.com/search?type=post&order=time&query=前端&subType=0&page=2' : url.split('?')[1].join('')
let r = new RegExp(/([^?&=]+)=([^?&=]+)/g)
var obj = {}
url.replace(r, function (res, $1, $2) {
console.log(res, $1, $2)
obj[$1] = $2
})
console.log(obj)
}
知识点:
因为JavaScript的Number类型是遵循IEEE 754规范表示的,这就意味着JavaScript能精确表示的数字是有限的,JavaScript可以精确到个位的最大整数是9007199254740992,也就是2的53次方,超过这个范围就会精度丢失,造成JavaScript无法判断大小。
function maxSum (str1, str2) {
if (typeof str1 !== 'string' || typeof str2 !== 'string') {
throw new Error('传入参数不是字符串')
}
let arr1 = str1.split('').reverse()
let arr2 = str2.split('').reverse()
let Len = Math.max(arr1.length, arr2.length)
let flag = 0
let res = []
for (let i = 0; i < Len; i++) {
arr1[i] = Number(arr1[i]) || 0
arr2[i] = Number(arr2[i]) || 0
let sum = arr1[i] + arr2[i] + flag
if (sum >= 10) {
flag = 1
sum = sum % 10
} else {
flag = 0
}
res.push(sum)
if (i === Len - 1 && flag) {
res.push(flag)
}
}
return res.reverse().join('')
}
function permutation (str) {
let res = [str[0]]
for (let i = 1; i < str.length; i++) {
res = fp(res, str[i])
}
return [...new Set(res)].sort()
}
function fp (arr, ch) {
let tmp = []
for (let i = 0; i <= arr[0].length; i++) {
tmp = tmp.concat(arr.map((x) => x.slice(0, i) + ch + x.slice(i)))
}
return tmp
}
// console.log(permutation('abc'));
知识点:
运用层序遍历的知识点来判断是否为平衡二叉树
function isCompleteTree (tree) {
let queue = []
queue.push(tree)
while (queue.length) {
let p = queue.shift()
if (p) {
queue.push(p.left)
queue.push(p.right)
} else {
while (queue.length) {
if (queue.shift()) {
return false
}
}
}
}
return true
}
ps:具体题目百度(面试常考算法)
// 两数之和
function twoSum (arr, sum) {
let flag = {}
for (let i = 0; i < arr.length; i++) {
if (!flag[arr[i]]) {
flag[sum - arr[i]] = arr[i]
} else {
return [arr[i], flag[arr[i]]]
}
}
}
// 三数之和
function threeSum (arr, sum) {
let res = []
if (!arr.length) return res
arr.sort((a, b) => a - b)
for (let i = 0; i < arr.length - 1; i++) {
if (arr[i] >= sum) return res
if (arr[i] === arr[i + 1]) continue
let L = i + 1
let R = arr.length - 1
while (L < R) {
if (arr[i] + arr[L] + arr[R] === sum) {
res.push([arr[i], arr[L], arr[R]])
while (L < R && arr[L] === arr[L + 1]) L++
while (L < R && arr[R] === arr[R - 1]) R--
L++
R--
}
else if (arr[i] + arr[L] + arr[R] < sum) L++
else if (arr[i] + arr[L] + arr[R] > sum) R--
}
}
return res
}