# 业务小代码
# 目录
DETAILS
- 实现 usePrevious
- 深度查询
- 私有属性实现,ES5、ES6
- 实现 fill(3, 4) 为 [4,4,4]
- 模拟实现 instanceof
- one(add(two())) 或 two(add(one())) 等于 3
- 斐波那契数列,使用 memo 做缓存,减少运算量
- new 的时候加 1
- 数组中 map 和 reduce,如何用 reduce 实现 map
- 统计字符串中出现最多的字母和次数
- 实现队列函数(先进先出),以实现一次 100 秒后打印出 1,200 秒后打印 2,300 秒后打印 3 这样
- setInterval 有两个缺点
- 实现一个 wait(1000, callback1).wait(3000, callback2).wait(1000, callback3)
- 实现成语接龙 wordschain('胸有成竹')('竹报平安')('安富尊荣').valueOf() 输出 胸有成竹 -> 竹报平安 -> 安富尊荣
- add(1, 3, 4)(7)(5, 5).valueOf();
- 实现 JSONP
- 返回 promise 的 JSONP 封装
- 实现 promisify
- 手写双向绑定
- 手写 vue observe 数据劫持
- 用 JS 模拟 DOM 结构(手写 vnode)
- 防抖节流
- promise 实现图片懒加载
- 二分查找
- 封装类型判断函数
- 如何效率的向一个 ul 里面添加 10000 个 li
- 快速排序
- Object.create
- 模拟实现 new 操作符
- 数组扁平化
- 简单版 EventEmitter 实现
- 优化版组合继承
- 创建 Ajax
- 实现字符串模板
- 深拷贝
- 事件代理
- 手写 bind 函数
- 实现一个函数 trim(str) 字符串前后去空格
- 如何用 ES5 实现 promise
- 手写文件上传
- 手写文件预览
- 写一个 DOM2JSON(node) 函数,node 有 tagName 和 childNodes 属性
- JS 实现一个带并发限制的异步调度器 Scheduler,保证同时运行的任务最多有 limit 个。完善下面代码的 Scheduler 类,使得一下程序能正确输出
- 编写 js 代码,实现点击一个 node 节点后,在 5s 内以当前位置为起点,半径为 100px,旋转一周
- 1000 瓶毒药里面只有 1 瓶是有毒的,毒发时间为 24 个小时,问需要多少只老鼠才能在 24 小时后试出那瓶有毒
# details
业务小代码
// 实现usePrevious
function usePrevious(val) {
const ref = useRef();
useEffect(() => {
ref.current = val;
}, [val]);
return ref.current;
}
// useEffect很重要的一点是:它是在每次渲染之后才会触发的,是延迟执行的。而return语句是同步的,所以return的时候,ref.current还是旧值。
// eg.
function Component() {
const [count, setCount] = useState(0);
const previousCount = usePrevious(count);
return (
<div>
<button onClick={() => setCount(p => p +1)}>add</button>
{ count } | { previousCount }
</div>
)
}
// 深度查询
function get (source, path, defaultValue = undefined) {
// a[3].b -> a.3.b
const paths = path.replace(/\[(\S+)\]/g, '.$1').split('.').filter(key => key)
// const paths = path.split(/[[\].]/g).filter(key => key); // 这里可以直接用正则 [[\].] 或匹配
let result = source
for (const p of paths) {
// null 与 undefined 取属性会报错,所以使用 Object 包装一下
result = Object(result)[p]
if (result === undefined) {
return defaultValue
}
}
return result
}
get(object, 'a[3].b', undefined);
// - 私有属性实现
// ES6
var Person = (() => {
let _name = Symbol()
class Person {
constructor(name) {
this[_name] = name
}
get name() { // 使用 getter可以改变属性name的读取行为
return this[_name]
}
}
return Person
})()
// ES5
var Person = (() => {
var _name = '00' + Math.random() // 用一个随机数来做
function Person(name) {
this[_name] = name
}
Object.defineProperty(Person.prototype, 'name', {
get: function() {
return this[_name]
}
})
return Person
})()
// - 实现 fill(3, 4) 为 [4,4,4]
function fill(n, m) {
n--
if(n) {
return [m].concat(fill(n, m))
} else {
return m
}
}
// - 模拟实现instanceof
// left是对象,right是原型对象
function myInstanceof(left, right) {
//基本数据类型直接返回false
if (typeof left !== 'object' || left == null) return false;
//getPrototypeOf是Object对象自带的一个方法,能够拿到参数的原型对象
let proto = Object.getPrototypeOf(left);
while (true) {
// 如果查找到尽头,还没找到,return false
if (proto == null) return false;
//找到相同的原型对象
if (proto === right.prototype) return true;
proto = Object.getPrototypeOf(proto);
}
}
console.log(myInstanceof("111", String)); //false
console.log(myInstanceof(new String("111"), String)); //true
// - one(add(two())) 或 two(add(one())) 等于3
console.log(one(add(two()))); // 3
console.log(two(add(one()))); // 3
function add(val) {
// console.log(val);
let num = val;
return x => {
return x + num;
};
}
function one(cb) {
const num = 1;
if (cb) {
return cb(num);
}
return num;
}
function two(cb) {
const num = 2;
if (cb) {
return cb(num);
}
return num;
}
// - 斐波那契数列,使用memo做缓存,减少运算量
// 动态规划的题也能使用这种方法做优化
const fib4 = (function () {
var memo = [0, 1];
return function _fib(n) {
var result = memo[n];
if (typeof result !== "number") {
memo[n] = _fib(n - 1) + _fib(n - 2);
result = memo[n];
}
return result;
};
})();
console.log(fib4(9)); // 34
// 尾递归实现fibonacci (尾调用优化)
// 函数最后一步操作是 return 另一个函数的调用,函数不需要保留以前的变量
function fib3(n, n1 = 1, n2 = 1){
if (n <= 2) {
return n2;
} else {
return fib3(n-1, n2, n1 + n2);
}
}
console.log(fib3(9)) // 34
// ### new的时候加1
const fn = (() => {
let count = 0
return function _fn() {
if (this.constructor == _fn) { // es5中判断是new调用
// if (new.target) { // es6中判断是new调用
console.log(`new了${++count}次`)
} else {
console.log('普通函数执行')
}
}
})()
// ### 数组中map和reduce,如何用reduce实现map
arr1.map((cur, index, sourceArr) => {
}, callbackThis)
arr1.reduce((prev, cur, index, sourceArr) => {
}, initial)
Array.prototype._map = function(fn, callbackThis) {
let res = []
let CBThis = callbackThis || null;
this.reduce((prev, cur, index, sourceArr) => {
res.push(fn.call(CBThis, cur, index, sourceArr))
}, null)
return res
}
// ### 统计字符串中出现最多的字母和次数
function findMaxChar(str) {
let obj = {}
for(let i = 0; i < str.length; i++) {
if (obj[str[i]]) {
obj[str[i]]++
} else {
obj[str[i]] = 1
}
}
let keys = Object.keys(obj)
let maxKey = '', maxCount = -1;
for(let i = 0; i < keys.length; i++) {
if (obj[keys[i]] > maxCount) {
maxCount = obj[keys[i]]
maxKey = keys[i]
}
}
return {
char: maxKey,
count: maxCount
}
}
// ### 实现队列函数(先进先出),以实现一次100秒后打印出1,200秒后打印2,300秒后打印3这样
// #### setInterval有两个缺点
// - 某些间隔会被跳过
// - 可能多个定时器会连续执行
// 这是因为每个 setTimeout 产生的任务会直接 push 到任务队列中,而 setInterval 在每次把任务 push 到任务队列时,都要进行一次判断(判断 上次的任务是否仍在队列中,是则跳过)。所以通过用 setTimeout 模拟 setInterval 可以规避上面的缺点。
const moniInterval = (fn, time) => {
const interval = () => {
setTimeout(interval, time)
fn()
}
setTimeout(interval, time)
}
const queue = () => {
let count = 1
const interval = () => {
console.log(count++)
setTimeout(interval, 1000 * count)
}
setTimeout(interval, 1000 * count)
}
queue()
// ### 实现一个wait(1000, callback1).wait(3000, callback2).wait(1000, callback3)
const wait = (time, callback) => {
let timeout = 0
const createChain = (t, cb) => {
timeout += t
setTimeout(cb, timeout)
return {
wait: createChain
}
}
return createChain(time, callback)
}
// ### 实现成语接龙 wordschain('胸有成竹')('竹报平安')('安富尊荣').valueOf() 输出 胸有成竹 -> 竹报平安 -> 安富尊荣
const wordschain = (...args) => {
let allArgs = []
const createChain = (...args) => {
allArgs = [...allArgs, ...args]
return createChain
}
createChain.valueOf = () => {
console.log(allArgs.join(' => '))
}
return createChain(...args)
}
// ### add(1, 3, 4)(7)(5, 5).valueOf();
const add = (...args) => {
let total = []
const createChain = (...args) => {
total = [...total, ...args]
return createChain
}
createChain.valueOf = () => {
console.log(total.reduce((a, b) => a + b, 0))
}
return createChain(...args)
}
// 实现JSONP
function JSONP(url, params = {}, callbackKey = 'cb', callback) {
// 定义本地的唯一callbackId,若是没有的话则初始化为1
JSONP.callbackId = JSONP.callbackId || 1;
JSONP.callbacks = JSONP.callbacks || [];
// 把要执行的回调加入到JSON对象中,避免污染window
let callbackId = JSONP.callbackId;
JSONP.callbacks[callbackId] = callback;
params[callbackKey] = `JSONP.callbacks[${callbackId}]` // 把设定的函数名称放入到参数中,'cb=JSONP.callbacks[1]'
const paramString = Object.keys(params).map(key => {
return `${key}=${encodeURIComponent(params[key])}`
}).join('&')
const script = document.createElement('script')
script.setAttribute('src', `${url}?${paramString}`)
document.body.appendChild(script)
JSONP.callbackId++ // id自增,保证唯一
}
JSONP({
url: 'http://localhost:8080/api/jsonp',
params: {
id: 1
},
callbackKey: 'cb',
callback(res) {
console.log(res)
}
})
// 后端会将数据传参到拿来的函数,赋值给响应体。。。前端拿到的就是一个'JSONP.callbacks[1](data)'这样的字符串,script加载完脚本后立即执行,就能拿到数据了
this.body = `${callback}(${JSON.stringify(callbackData)})`
// 返回promise的JSONP封装
function JSONP(url, params = {}, callbackKey = 'cb') {
return new Promise((resolve, reject) => {
// 定义本地的唯一callbackId,若是没有的话则初始化为1
JSONP.callbackId = JSONP.callbackId || 1;
JSONP.callbacks = JSONP.callbacks || [];
// 把要执行的回调加入到JSON对象中,避免污染window
let callbackId = JSONP.callbackId;
params[callbackKey] = `JSONP.callbacks[${callbackId}]` // 把设定的函数名称放入到参数中,'cb=JSONP.callbacks[1]'
const paramString = Object.keys(params).map(key => {
return `${key}=${encodeURIComponent(params[key])}`
}).join('&')
const script = document.createElement('script')
script.setAttribute('src', `${url}?${paramString}`)
// 注册全局函数,等待执行
JSONP.callbacks[callbackId] = result => {
// 一旦执行,就要删除js标签和注册的函数
delete JSONP.callbacks[callbackId]
document.body.removeChild(script)
if (result) {
resolve(result);
} else {
reject(new Error('没有返回数据'))
}
}
// js加载异常的情况
script.addEventListener('error', () => {
delete JSONP.callbacks[callbackId]
document.body.removeChild(script)
reject(new Error('JavaScript资源加载失败'))
}, false)
// 添加js节点到document上时,开始请求
document.body.appendChild(script)
JSONP.callbackId++ // id自增,保证唯一
})
}
// 实现promisify
const promisify = (func) => (...args) =>
new Promise((resolve, reject) => {
func(...args, (err, result) => {
if (err) {
reject(err);
} else {
resolve(result);
}
})
})
// 或者
const promisify = function(func) {
return function(...args) {
var ctx = this
return new Promise((resolve, reject) => {
func.call(ctx, ...args, (err, result) => {
if (err) {
reject(err);
} else {
resolve(result);
}
})
})
}
}
// nodeCallback方法func1
var func1 = function(a, b, c, callback) {
callback(null, a+b+c);
}
// promise化后的func2
var func2 = promisify(func1);
// 调用后输出6
func1(1, 2, 3, (err, result) => {
if (!err) {
console.log(result); //输出6
}
})
func2(1, 2, 3).then(console.log); //输出6
// 原有的callback调用方式
fs.readFile('test.js', function(err, data) {
if (!err) {
console.log(data);
} else {
console.log(err);
}
});
// promisify后调用方式
var readFileAsync = promisify(fs.readFile);
readFileAsync('test.js').then(data => {
console.log(data);
}, err => {
console.log(err);
});
// 只有nodeCallback方法可以通过 promisify 变成 promise,nodeCallback需满足两个条件
// - 1、回调函数在主函数中的参数位置必须是最后一个;
// - 2、回调函数参数中的第一个参数必须是 error。
// var func = function(a, b, c, callback) {
// callback(null, a+b+c);
// }
// [Callback 与 Promise 间的桥梁 —— promisify](https://juejin.im/post/59f99d916fb9a0450b65b538)
// ### 手写双向绑定
const input = document.getElementById('input')
const obj = { val: '' }
Object.defineProperty(obj, 'val', {
get() {
return input.value
},
set(value) {
input.value = value
},
enumerable: false,
configurable: false
})
input.addEventListner('input', function(ev) {
obj.val = ev.target.value
})
// 手写vue observe数据劫持
class Vue {
constructor(options) {
//缓存参数
this.$options = options;
//需要监听的数据
this.$data = options.data;
//数据监听
this.observe(this.$data);
}
observe(value) {
if (!value || typeof value !== 'object') {
return;
}
/*
取到每个key和value 调用definReactive 进行数据劫持
*/
Object.keys(value).forEach(key => {
//深度监听
if (typeof value[key] === 'object') {
this.observe(value[key]);
}
this.defineReactive(value, key, value[key]);
})
}
defineReactive(obj, key, val) {
Object.defineProperty(obj, key, {
get() {
return val;
},
set(newVal) {
if (newVal === val) {
return;
}
val = newVal;
console.log(`${key}属性更新了:${val}`);
}
})
}
}
// 用JS模拟DOM结构(手写vnode)
<template>
<div id="div1" class="container">
<p>vdom</p>
</div>
</template>
{
tag: 'div',
props: {
className: 'container',
id: 'div1'
},
children: [
{
tag: 'p',
children: 'vdom'
}
]
}
// ### 防抖节流
const debounce = (fn, time) => {
let timer
return function(...args) {
let that = this
clearTimeout(timer)
timer = setTimeout(() => {
fn.apply(that, [...args])
}, time)
}
}
const throttle = (fn, time) => {
let canDo = true;
return function(...args) {
if (!canDo) {
return
}
canDo = false
let that = this
setTimeout(() => {
fn.apply(that, [...args])
canDo = true
}, time)
}
}
// ### promise实现图片懒加载
function loadImg(url) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = function() {
console.log("一张图片加载完成");
resolve(img);
};
img.onerror = function() {
reject(new Error('Could not load image at' + url));
};
img.src = url;
})
};
// ### 二分查找
function binarySearch(target, arr, start, end) {
if (start > end) return -1
let mid = Math.floor((start + end) / 2)
if (target === arr[mid]) {
return mid
} else if (target >= arr[mid]) {
return binarySearch(target, arr, mid + 1, end)
} else {
return binarySearch(target, arr, start, mid - 1)
}
}
// ### 封装类型判断函数
function getType(data) {
let type = typeof data
if (type !== 'object') {
return type
}
// return Object.prototype.toString.call(data).match(/\s(\w+)/)[1].toLowerCase()
return Object.prototype.toString.call(data).replace(/^\[object (\w+)\]$/, "$1").toLowerCase()
}
"[object Array]".match(/\s(\w+)/)
// ### 如何效率的向一个ul里面添加10000个li
// 方法1:使用fragment
let ul = document.getElementById('ul')
let fragment = document.createDocumentFragment()
for(let i = 0, li; i < 10000; i++) {
li = document.createElement('li')
li.innerHTML = '是个li'
fragment.appendChild(li)
}
ul.appendChild(fragment)
// 方法2:使用字符串拼接
let ul = document.getElementById('ul')
let str = ''
for(let i = 0; i < 10000; i++) {
str += '<li>是个li</li>'
}
ul.innerHTML(str)
// ### 快速排序
const quickSort = (arr) => {
return arr.length <= 1 ? arr : quickSort(arr.slice(1).filter(item => item < arr[0])).concat(arr[0], quickSort(arr.slice(1).filter(item => item >= arr[0])))
}
// ### 实现Object.create
Object.create = Object.create || function(obj) {
var F = function() {};
F.prototype = obj;
return new F()
}
// ### 模拟实现new操作符
var thisNew = function(M) {
// Object.create()返回空对象o,并关联M的原型对象
var o = Object.create(M.prototype);
// 执行构造函数M,并绑定作用域到o上
var res = M.call(o);
// 判断res是否是广义上的对象,是则返回res,否则返回o
if (res instanceof Object) { // 使用typeof不行,因为需要排除null的情况
return res;
} else {
return o;
}
}
// ### 数组扁平化
function flatten(arr) {
let newArr = []
for(let i = 0; i < arr.length; i++) {
if (Array.isArray(arr[i])) {
newArr = newArr.concat(flatten(arr[i]))
} else {
newArr.push(arr[i])
}
}
return newArr
}
[1, [2, [3, 4]]].toString().split(',').map(i => Number(i))
function flatten(arr) {
while(arr.some(item => Array.isArray(item))) {
arr = [].concat(...arr)
}
return arr
}
// ### 数组去重
Array.from(new Set(arr))
[...new Set(arr)]
// ### 简单版EventEmitter实现
class EventEmitter {
constructor() {
this._eventBus = {}
}
on(type, cb) {
if (!this._eventBus[type]) {
this._eventBus[type] = []
}
this._eventBus[type].push(cb)
}
emit(type, ...args) {
if (this._eventBus[type]) {
this._eventBus[type].forEach(cb => {
cb(...args)
})
}
}
off(type, cb) {
if (this._eventBus[type]) {
let index = this._eventBus[type].indexOf(_cb => _cb === cb)
index > -1 && this._eventBus[type].splice(index, 1)
}
}
once(type, cb) {
this.on(type, (...args) => {
cb(...args)
this.off(type)
})
}
}
// 优化版
class EventEmitter {
constructor() {
this._eventBus = {}
}
on(type, cb) {
let handler = this._eventBus[type]
if (!handler) {
this._eventBus[type] = cb
} else if (handler && typeof handler === 'function') {
this._eventBus[type] = [handler, cb]
} else {
this._eventBus[type].push(cb)
}
}
emit(type, ...args) {
let handler = this._eventBus[type]
if (handler && Array.isArray(handler)) {
this._eventBus[type].forEach(cb => {
args.length > 0 ? cb.apply(this, args) : cb.call(this)
})
} else {
// 单个函数直接触发
args.length > 0 ? handler.apply(this, args) : handler.call(this)
}
}
off(type, cb) {
let handler = this._eventBus[type]
if (handler && typeof handler === 'function') {
delete this._eventBus[type]
} else {
let index = this._eventBus[type].findIndex(_cb => _cb === cb)
if (index > -1) {
this._eventBus[type].splice(index, 1)
if (handler.length === 1) {
this._eventBus[type] = handler[0]
}
}
}
}
once(type, cb) {
this.on(type, (...args) => {
args.length > 0 ? cb.apply(this, args) : cb.call(this)
this.off(type)
})
}
}
// 进阶版
class EventEmitter {
constructor() {
this._eventBus = this._eventBus || new Map()
}
on(type, cb) {
let handler = this._eventBus.get(type)
if (!handler) {
this._eventBus.set(type, cb)
} else if (handler && typeof handler === 'function') {
this._eventBus.set(type, [handler, cb])
} else {
handler.push(cb)
}
}
emit(type, ...args) {
let handler = this._eventBus.get(type)
if (handler && Array.isArray(handler)) {
for (let i = 0; i < handler.length; i++) {
Reflect.apply(handler[i], this, args)
}
} else {
Reflect.apply(handler, this, args)
}
}
off(type, cb) {
let handler = this._eventBus.get(type)
if (handler && typeof handler === 'function') {
this._eventBus.delete(type)
} else {
let index = handler.findIndex(_cb => _cb === cb)
if (index > -1) {
handler.splice(index, 1)
if (handler.length === 1) {
this._eventBus.set(type, [handler[0]])
}
}
}
}
once(type, cb) {
let handler = this._eventBus.get(type)
this.on(type, (...args) => {
Reflect.apply(handler, this, args)
this.off(type)
})
}
}
// ### 优化版组合继承
function Parent() {
this.type = 'Parent';
this.habit = [1, 2, 3]
}
Parent.prototype.say() = function() { // 这里
console.log('chichihehe')
}
function Child() {
Parent.call(this) // 这里
this.type = 'Child';
}
Child.prototype = Object.create(Parent.prototype) // 这里
Child.prototype.constructor = Child // 这里
// 创建Ajax
var Ajax = {
get(url, fn) {
var xhr = XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP')
xhr.open('GET', url, true)
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && (xhr.status === 200 || xhr.status === 304 || xhr.status === 206)) {
fn.call(this, xhr.responseText)
}
}
xhr.send()
},
post(url, data, fn) {
var xhr = XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP')
xhr.open('POST', url, true)
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && (xhr.status === 200 || xhr.status === 304 || xhr.status === 206)) {
fn.call(this, xhr.responseText)
}
}
xhr.send(data)
}
}
// 实现字符串模板
const template = "I am {{name }}, {{ age}} years old";
var context = { name: "xiaoming", age: 2 };
function templateStr(template, context) {
return template.replace(/\{\{(.*?)\}\}/g, (match, key) => {
return context[key.replace(/\s/g, '')];
});
}
console.log(templateStr(template, context));
// 千分位题
function toThousands(num) {
return (num || 0).toString().replace(/(\d)(?=(?:\d{3})+$)/g, '$1,')
}
toThousands(123456789011)
// 写一个方法,实现字符串从后往前每三个插入|,得到"ad|abc|def|ghi"
const str = "adabcdefghi"
let newStr = str.replace(/(\w)(?=(?:\w{3})+$)/g, "$1|");
console.log(newStr);
// 深拷贝
// 浅拷贝与深拷贝的区别
// 深拷贝
// - 最简单版本:只对JSON安全的数据结构有效;且会抛弃对象的constructor,所有的构造函数会指向Object;遇到对象有循环引用,会报错。
// - 只能写出简单版本,即只实现到区分array与Object的引用类型
// - 如果考虑全面类型的话,对Date、RegExp、甚至function都是要考虑的(当然这里的function其实考虑了也没意义,两个对象使用在内存中处于同一个地址的函数也是没有任何问题的,而比如lodash在碰到函数深拷贝时就直接返回了)
// - 另外还应考虑循环引用的问题
// - 解决循环引用问题,需额外开辟一个存储空间,来存储当前对象和拷贝对象的对应关系,当需要拷贝当前对象时,先去存储空间中找,有没有拷贝过这个对象,如果有的话直接返回,如果没有的话继续拷贝,这样就巧妙化解的循环引用的问题。
// 这个存储空间,需要可以存储key-value形式的数据,且key可以是一个引用类型,我们可以选择Map这种数据结构。
function deepClone(obj) {
if (typeof obj !== 'object' || obj === null) {
return obj
}
let newObj = Object.prototype.toString.call(obj) === '[object Array]' ? [] : {}
if (window.JSON) {
newObj = JSON.parse(JSON.stringify(obj))
} else {
for(let k in obj) {
newObj[k] = typeof obj[k] === 'object' ? deepClone(obj[k]) : obj[k]
}
}
return newObj
}
// ### 事件代理
// - 事件代理利用两个js事件特性:事件冒泡、目标元素。
// - 使用事件代理的话我们可以把事件处理器添加到一个祖先元素上,等待事件从它的子级元素里冒泡上来,并且可以很方便地判断出这个事件是从哪个元素开始的。
var ul = document.getElementById("ul");
ul.addEventListener('click', function(e) {
if (e.target && e.target.tagName.toLowrCase() === 'li') {
//需要执行的代码
}
}, false);
// 手写bind函数
// 只实现返回函数 与 分开传参数
Function.prototype._bind = function(ctx) {
const args = Array.prototype.slice.call(arguments, 1)
let self = this
return function() {
const bindArgs = Array.prototype.slice.call(arguments)
self.apply(ctx, args.concat(bindArgs))
}
}
// 还要模拟 当做构造函数使用
Function.prototype._bind = function(ctx) {
const args = Array.prototype.slice.call(arguments, 1)
let self = this
let res = function() {
const bindArgs = Array.prototype.slice.call(arguments)
self.apply(this instanceof self ? this : ctx, args.concat(bindArgs))
}
const Foo = function() {}
Foo.prototype = this.prototype
res.prototype = new Foo()
return res
}
// Q:实现一个函数trim(str) 字符串前后去空格
String.prototype.trim = function() {
return this.replace(/(^\s*)|(\s*$)/g, '')
}
// - 如何用ES5实现promise
class Promise {
constructor(executor) {
this.status = 'pending'
this.value = undefined
this.reason = undefined
let resolve = function(value) {
if (this.status === 'pending') {
this.value = value
this.status = 'fulfiled'
}
}
let reject = function(reason) {
if (this.status === 'pending') {
this.reason = reason
this.status = 'rejected'
}
}
try {
executor(resolve, reject)
} catch(e) {
reject(e)
}
}
then(onFulfiled, onRejected) {
switch(this.status) {
case 'fulfiled':
onFulfiled()
break
case 'rejected':
onRejected()
break
default:
}
}
}
// - 手写文件上传
const input = document.createElement('input')
document.querySelector('body').appendChild(input)
input.click()
setTimeout(() => {
input.remove()
}, 1000)
input.onchange = function() {
let file = input.files[0]
let fd = new FormData()
fd.append('file', file)
fd.append('filename', file.name)
fd.append('other', file.xxx)
let xhr = new XMLHttpRequest()
let action = 'http://xxxx.com/upload'
xhr.open('POST', action, true)
xhr.send(fd)
xhr.onreadystatechange = function() {
}
}
// - 手写文件预览
var file = document.getElementById('file')
var img = document.getElementById('img')
file.addEventListener('change', function(){
var obj = file.files[0]
var src = window.URL.createObjectURL(obj)
img.setAttribute('src', src);
})
var file = document.getElementById('file')
var img = document.getElementById('img')
file.addEventListener('change', function(){
var obj = file.files[0]
var reader = new FileReader();
reader.readAsDataURL(obj);
reader.onloadend = function() {
img.setAttribute('src', reader.result);
}
})
// 写一个 DOM2JSON(node) 函数,node 有 tagName 和 childNodes 属性
`
<div>
<span>
<a></a>
</span>
<span>
<a></a>
<a></a>
</span>
</div>
{
tag: 'DIV',
children: [{
tag: 'SPAN',
children: [
{ tag: 'A', children: [] }
]
}, {
tag: 'SPAN',
children: [
{ tag: 'A', children: [] },
{ tag: 'A', children: [] }
]
}]
}
`
function DOM2JSON(node) {
var obj = {};
obj['tag'] = node.nodeName;
obj['children'] = [];
var child = node.children;
for (var i = 0; i < child.length; i++) {
obj['children'].push(DOM2JSON(child[i]))
}
return obj;
}
// JS实现一个带并发限制的异步调度器Scheduler,保证同时运行的任务最多有limit个。完善下面代码的Scheduler类,使得以下程序能正确输出
class Scheduler {
add(promiseCreator) {
//...
}
}
function timeout(time){
return new Promise(resolve=>{
setTimeout(resolve,time)
})
}
var scheduler = new Scheduler()
function addTask(time,order){
scheduler.add(()=>timeout(time).then(()=>console.log(order)))
}
addTask(1000,1)
addTask(500,2)
addTask(300,3)
addTask(400,4)
// 2
// 3
// 1
// 4
// 这里说明传入的limit是2,并发执行2个
class Scheduler {
constructor(limit = 2) {
this.queue = [];
this.limit = limit;
this.currentTaskCount = 0;
}
add(fn) {
return new Promise((resolve, reject) => {
this.queue.push([fn, resolve]);
this.run();
})
}
run() {
if (this.currentTaskCount < this.limit && this.queue.length) {
const [fn, resolve] = this.queue.shift();
this.currentTaskCount++;
Promise.resolve(fn()).then((result) => {
resolve(result);
this.currentTaskCount--;
this.run();
})
}
}
}
function timeout(time){
return new Promise(resolve=>{
setTimeout(resolve,time)
})
}
var scheduler = new Scheduler(2)
function addTask(time, order){
scheduler.add(()=>timeout(time).then(()=>console.log(order)))
}
addTask(1000,1);
addTask(500,2);
addTask(300,3);
addTask(400,4);
// 编写js代码,实现点击一个node节点后,在5s内以当前位置为起点,半径为100px,旋转一周。
// 提示:
// 1. 要求使用js实现动画和旋转,不可以使用transform:rotate()/animtion/transition等css3属性
// 2. 提示:已知圆周为2PI; 弧度= 角度* Math.PI / 180 ;js中Math对象下有sin(弧度) cos(弧度)等常用三角函数方法。例如获取30度角的正弦值:Math.sin(30 * Math.PI / 180)
function rotate360(node) {
const radius = 100; // 旋转半径
const duration = 5 * 1000; // 5s
const startTime = Date.now();
const run = () => {
const elapse = Date.now() - startTime;
if (elapse <= duration) {
const radian = elapse / duration * 2 * Math.PI; // 当前旋转的弧度
const x = Math.sin(radian) * radius;
const y = (1 - Math.cos(radian)) * radius;
node.style.transform = `translate(${x}px, ${y}px)`
requestAnimationFrame(run);
}
}
run();
}
// 1000瓶毒药里面只有1瓶是有毒的,毒发时间为24个小时,问需要多少只老鼠才能在24小时后试出那瓶有毒
← promise 专题 leetcode部分 →