[Js茶话会]-Js的N道试炼

273 阅读4分钟

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

// 先定义三个常量表示状态
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;
    }
}