Js的N道试炼
前面进行了好几个专题的分析,大家都吃好喝好,现在来进行我们的试炼吧!
手写Call,Apply,bind
- bind
Function.prototype.myBind = function (context, args) {
_this = this
return function () {
_this.apply(context, args)
}
}
function A (m, n) {
console.log(this.name)
console.log('m',m)
console.log('n',n)
}
const B = {
name: 'lucy'
}
const test = A.myBind(B, [1,2])
- apply
Function.prototype.myApply = function (context, args) {
context = context || window;
context.fn = this;
let result = context.fn(...args)
delete context.fn;
return result
}
function A(m, n) {
console.log(this.name)
console.log('m', m)
console.log('n', n)
}
const B = {
name: 'liili'
}
A.myApply(B, [1, 2])
- call
Function.prototype.myCall = function (context) {
context = context || window;
context.fn = this;
console.log('arguments',arguments)
const args = Array.from(arguments).slice(1)
let result = context.fn(...args)
delete context.fn;
return result
}
function A(m, n) {
console.log(this.name)
console.log('m', m)
console.log('n', n)
}
const B = {
name: 'liili'
}
A.myCall(B, 1,2)
数组扁平化
- 使用reduce和concat
let arr = [[2, 3, 4], 1, 4, [22, [11, 33]]]
const flatDeep = function(arr) {
return arr.reduce((total, current)=>{
if (Array.isArray(current)) {
return total.concat(flatDeep(current))
} else {
return total.concat(current)
}
},[])
}
- 利用generator特性
function* flat(arr) {
if (Array.isArray(arr)) {
for (let i = 0; i < arr.length; i++) {
yield* flat(arr[i]) //yield*执行generator函数
}
} else {
yield arr
}
}
console.log([...flat(arr)])
ul,li用事件代理实现点击效果
window.onload = function () {
//这里给ul添加事件
var oUl = document.getElementById('ul1');
oUl.addEventListener('click', function(e){
if(e.target.nodeName === 'LI') {
console.log(e.target.innerHTML)
}
})
}
链式调用
class Math {
constructor(value) {
if (value) {
this.value = value
} else {
this.value = 0
}
}
add() {
let args = [...arguments]
const value = args.reduce((total, current) => {
return total + current
}, this.value)
return new Math(value)
}
minus() {
let args = [...arguments]
const value = args.reduce((total, current) => {
return total - current
}, this.value)
console.log('value',value)
return new Math(value)
}
}
let math = new Math()
let res = math.add(1, 2).minus(3, 4)
console.log('data', res.value) // -4
add(1)(2)(3)
function add(...args) {
return args.reduce((a, b) => a + b)
}
function currying(fn) {
let args = []
return function _c(...newArgs) {
//前面几次调用只进行参数的收集
if (newArgs.length) {
args = [
...args,
...newArgs
]
return _c
} else {
//当最后调用的时候,args为[1,2,3,4,5]
return fn.apply(this, args)
}
}
}
let addCurry = currying(add)
// 注意调用方式的变化
console.log(addCurry(1)(2)(3)(4, 5)())
防抖节流
防抖
- 事件触发N秒后执行回调,如果再次被触发,则重新计时。
- 场景
- 按钮提交
- 输入框联想搜索
- 代码
function debounce(fun, wait){
let timeout = null
return function (){
timeout && clearTimeout(timeout)
const args = arguments
timeout = setTimeout(()=>{
fun.apply(this, args)
},wait)
}
}
节流
- 一段时间之内只触发一次,如果触发了多次,只有一次生效
- 适用场景: 监控浏览器resize等
function throttle(func, wait){
let time = 0
return function(){
let current = new Date().getTime()
if(current - time > wait) {
func.apply(this, arguments)
time = current
}
}
}
promisify
function promisify(fn) {
if ({}.toString.call(fn) !== '[object Function]') throw new TypeError('Only normal function can be promisified');
return function (...args) {
return new Promise((resolve, reject) => {
const callback = function (...args) {
const err = args.shift();
const rest = args;
if ({}.toString.call(err) === '[object Error]') return reject(err);
if (rest.length === 1) return resolve(rest[0]);
return resolve(rest);
};
try {
fn.apply(null, [...args, callback]);
} catch (err) {
reject(err);
}
});
}
}
function sett(wait,callback) {
setTimeout(() => {
console.log(1000)
callback()
}, wait)
}
const pSet = promisify(sett)
pSet(1000).then(res => {
console.log('xx')
})
compose
- 返回函数集 functions 组合后的复合函数,比如a(),b(),c(),返回一个a(b(c()))
- compose的参数是函数,返回的也是一个函数。
- 因为除了第一个函数的接受参数,其他函数的接受参数都是上一个函数的返回值,所以初始函数的参数是多元的(本题只讨论了一元参数的情况),而其他函数的接受值是一元的。
- 执行方向是自右向左的,初始函数一定放到参数的最右面。
function compose (...funcs){
return funcs.reduce((a, b)=> {
return (...args)=> {
return a(b(...args))
}
})
}
function a(value) {
return value + 1
}
function b(value){
return value + 2
}
let funs = compose(a,b)
console.log(funs(1)) //4
lodash.get
function lodashGet(data, paths){
let defaultValue
let result = data
for(let path of paths){
if(result ===null || result === undefined) {
result = undefined
break
}
let pathData = result[path]
result = pathData
}
return result
}
let lodashData = {
person: {
age: 11
}
}
console.log(lodashGet(lodashData, ['person', 'age'])) //11
console.log(lodashGet(lodashData, ['person', 'name'])) //undefined
console.log(lodashGet(lodashData, ['cat', 'name'])) //undefined
instancof
- instanceof 在左边的原型链上有右侧的原型
function instanceOf(a, b){
let left = a.__proto__
let right = b.prototype
whilte(left){
if (left === right) {
return true
}
left = left.__proto__
}
return false
}
深拷贝与浅拷贝
- 深拷贝 这里列举了object,null,array等常见情况处理方法
function deepClone(data) {
let res = {}
for (const key in data) {
if (data.hasOwnProperty(key)) {
const element = data[key];
if (Array.isArray(element)) {
let arrayRes = []
element.forEach(item => {
arrayRes.push(deepClone(item))
})
res[key]=arrayRes
} else if (element === null) {
res[key] = null
} else if (typeof element === 'object') {
res[key] = deepClone(element)
} else {
res[key] = element
}
}
}
return res
}
const data = {
name: 'lili',
age: 22,
null: null,
undef: undefined,
children: [
{
name: 'lucy',
age: 2,
}
]
}
const res = deepClone(data)
console.log('res', res)
- 浅拷贝 Object.assing ...spread语法 Array.prototype.slice
EventEmit
class EventEmitter {
constructor() {
this.listeners = new Map()
}
emit(event) {
let allListeners = this.listeners.get(event)
let args = Array.from(arguments)
args = args.slice(1)
allListeners.forEach(element => {
element.apply(null, args)
});
}
addListener(event, listener) {
let allListeners = this.listeners.get(event)
if (!allListeners) {
allListeners = []
}
allListeners.push(listener)
this.listeners.set(event, allListeners)
}
removeListener(event, listener) {
let allListeners = this.listeners.get(event)
if (!allListeners) {
allListeners = []
}
let index = allListeners.indexof(listener)
if (index >= 0) {
allListeners.slice(index, 1)
}
this.listeners.set(event, allListeners)
}
}
let eventE = new EventEmitter()
eventE.addListener('test', function () {
console.log('222')
})
eventE.addListener('test', function () {
console.log('333')
})
eventE.emit('test')
图片懒加载
懒加载的关键就是,在图片没有进入可视区域时,先不给的src赋值,这样浏览器就不会发送请求了,等到图片进入可视区域再给src赋值
- 给所有图片一个默认的loading图片,和data-src属性存放真实图片地址
- 判断图片进入可视区域
- 替换src为data-src
- 通过getBoundingClientRect来判断是否在可视范围内
let imgs = document.querySelectorAll('.img')
function init() {
imgs.forEach(function(item) {
// 浏览器窗口top位置
let windowTop = document.documentElement.clientTop
// 浏览器窗口高度
let windowHeight = document.documentElement.clienHeight
// 图片距离窗口的值
let imgsTop = item.getBoundingClientRect().top
if (imgsTop - windowTop <= windowHeight) {
// 获取保存到data-src 的图片地址
let src = item.getAttribute('data-src')
// 再设置他的src值
item.setAttrbute('src', src)
}
})
}
// 初始化
init()
// 滚动监听
window.addEventerListener('scroll', init)
- 通过新的Api IntersectionObserver
let imgs = document.querySelectorAll('.img')
const option = {
}
function init(target) {
const intersectionObserver = new IntersectionObserver(
function(entries, observer){
entries.forEach(function(entry){
// 当root 元素与目标元素相交 isIntersectiong 为true,
if (entry.isIntersecting) {
// 获取保存到data-src 的图片地址
const src = entry.getAttribute('data-src')
// 再设置他的src值
entry.setAttrbute('src', src)
// 断开连接
observer.disconnect()
}
}
}, option)
intersectionObserver.observer(target)
}
imgs.forEact(function(item) {
init(item)
}
手写Promise
- promise有三个状态: fullfilled,rejected,pending,状态一旦改变则不能再次改变
- .then返回promise .then(data, error)
- 构建promise立即执行,.then里面的方法异步执行
- 以下代码是这位大佬的Promise简易实现,应对面试使用
// 先定义三个常量表示状态
var PENDING = 'pending';
var FULFILLED = 'fulfilled';
var REJECTED = 'rejected';
function MyPromise(fn) {
this.status = PENDING; // 初始状态为pending
this.value = null; // 初始化value
this.reason = null; // 初始化reason
// 构造函数里面添加两个数组存储成功和失败的回调
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
// 存一下this,以便resolve和reject里面访问
var that = this;
// resolve方法参数是value
function resolve(value) {
if (that.status === PENDING) {
that.status = FULFILLED;
that.value = value;
// resolve里面将所有成功的回调拿出来执行
that.onFulfilledCallbacks.forEach(callback => {
callback(that.value);
});
}
}
// reject方法参数是reason
function reject(reason) {
if (that.status === PENDING) {
that.status = REJECTED;
that.reason = reason;
// resolve里面将所有失败的回调拿出来执行
console.log('that.onRejectedCallbacks', that.onRejectedCallbacks)
that.onRejectedCallbacks.forEach(callback => {
callback(that.reason);
});
}
}
try {
fn(resolve, reject);
} catch (error) {
reject(error);
}
}
MyPromise.prototype.then = function (onFulfilled, onRejected) {
var realOnFulfilled = onFulfilled;
var realOnRejected = onRejected;
var that = this; // 保存一下this
// 如果还是PENDING状态,将回调保存下来
if (this.status === PENDING) {
var promise2 = new MyPromise(function (resolve, reject) {
that.onFulfilledCallbacks.push(function () {
setTimeout(function () {
try {
if (typeof onFulfilled !== 'function') {
resolve(that.value);
} else {
var x = realOnFulfilled(that.value);
resolve(x)
}
} catch (error) {
console.log('err')
reject(error);
}
}, 0);
});
that.onRejectedCallbacks.push(function () {
setTimeout(function () {
try {
if (typeof onRejected !== 'function') {
reject(that.reason);
} else {
var x = realOnRejected(that.reason);
reject(x)
}
} catch (error) {
reject(error);
}
}, 0)
});
});
return promise2;
}
}