模拟实现js函数

208 阅读8分钟

实现一个call函数

// 思路:将要改变this指向的方法挂在目标this上执行并返回
//  func.call(thisObj, arg1, arg2, ...) 绑定this之后为参数,以正常参数形式传
Function.prototype.myCall = function(context) {
    if(typeof this !== 'function') {
        throw new TypeError('not function');
    };
    context = context || window;
    context.fn = this;
    // 这里是因为arguments只是类数组对象,并没有slice方法
    let args = [...arguments].slice(1);
    let result = context.fn(...args);
    delete context.fn;
    return result;
}

实现一个apply函数

// 思路:将要改变this指向的方法挂在目标this上执行并返回
// func.apply(thisObj, [arg1, arg2, ...., argn]), 待绑定this对象之后,参数以数组形式传
Function.prototype.myApply = function(context) {
    if(typeof this !== 'function') {
        throw new TypeError('not function');
    }
    
    context = context || window;
    context.fn = this;
    let result = null;
    // 这里的arguments[1]为一个数组,故下面用扩展运算符展开
    if(arguments[1]) {
        result = context.fn(...arguments[1])
    } else {
        context.fn()
    };
    delete context.fn;
    return result;
}

实现一个bind函数

// 思路:类似call,但返回的是一个函数
Function.prototype.myBind = function(context) {
    if(typeof this !== 'function') {
        throw new TypeError('not function')
    }
    context = context || window;
    let _this = this;
    let args = [...arguments].slice(1);
    return function F() {
        // 处理函数使用new的情况,如果使用new调用返回的函数。此时this就是F的一个实例,则this instance F 为true。
        //将第一次context除外的参数和第二次的参数全部传进构造函数中
        if(this instanceof F) {
            return new _this(...args, ...arguments);
        } else {
        // 常规情况下,利用apply进行绑定this。call也可以只是传参形式不一样
            return _this.apply(context, args.concat(...arguments));
        }
    }
}

instanceof的原理

// 思路:右边变量的原型存在于左边变量的原型链上
function myInstanceOf(instance, constructor) {
    let leftProto = instance.__proto__;
    let rightProto = constructor.prototype;
    // 沿着实例的原型链依次与构造函数原型做比较
    while(leftProto) {
        if(leftProto === rightProto) {
            return true;
        }
        leftProto = leftProto.__proto__;
    }
    return false;
}

Object.create的基本实现原理

// 思路:将传入的对象作为原型
function myCreate(obj) {
    function F(){}
    F.prototype = obj;
    return new F();
}

new 本质

// new运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例。
// new关键字会进行如下的操作
//  1. 创建一个空的简单的js对象,即{}
//  2. 将构造函数的原型设置为创建空对象的原型。
//  3. 将步骤1新创建的对象作为this的上下文
//  4. 根据传的参数进行实例化
//  5. 如果该函数没有返回对象,则返回创建的对象
function myNew(constructor) {
    return function() {
        // 创建一个新对象且将其隐式原型指向构造函数原型
        let obj = Object.create(constructor.prototype);
        
        // 根据参数进行实例化,正常情况
        //constructor.call(obj, ...arguments);
        //return obj;
        
        // 若构造函数中return了一个对象类型数据,则会new 操作会返回该对象,实例化无效
        let obj1 = constructor.call(obj, ...arguments);
        
        return typeof obj1 === 'object' ? obj1 : obj; 
    }
}

实现一个基本的Promise

// 未添加异步处理等其他边界情况
// 1.自动执行函数 2.三个状态 3. then返回的不是promise
// 基本版
class MyPromise {
    constructor (fn) {
        // 三个状态
        this.state = 'pending';
        this.value = undefined;
        this.reason = undefined;
        let resolve = value => {
            if(this.state === 'pending') {
                this.state = 'fulfilled';
                this.value = value;
                
            }
        }
        let reject = reason => {
            if(this.state === 'pending') {
                this.state = 'rejected';
                this.reason = reason;
            }
        }
        
        try {
            fn(resolve, reject);
        } catch(e) {
            reject(e)
        }
    }
    
    then(onFulfilled, onRejected) {
        switch(this.state) {
            case 'fulfilled':
                onFulfilled(this.value);
                break;
            case 'rejected':
                onRejected(this.reason);
                break;
            default
        }
    }
}

// 大厂专供版(自己理解还有点问题,回头再看下)

const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

// promise 接受一个函数参数,该函数参数会立即执行
function MyPromise() {
    let _this = this;
    _this.currentState = PENDING;
    _this.value = undefined;
    // 用于保存then中的回调,只有当promise
    // 抓鬼太为pending时才会缓存,并且每个实例至多缓存一个
    _this.resolvedCallbacks = [];
    _this.rejectedCallbacks = [];
    _this.resolve = function (value) {
        if(value instanceof MyPromise) {
            // 如果value是Promise,递归执行
            return value.then(_this.resolve, _this.reject);
        }
        // 异步执行,保证执行顺序
        setTimeout(() => {
            if(_this.currentState === PENDING) {
                _this.currentState = RESOLVED;
                _this.value = value;
                this.resolvedCallbacks.forEach(cb => cb())
            }
        })
    }
    
    _this.reject = function (reason) {
        setTimeout(() => {
            if(_this.currentState === PENDING) {
                _this.currentState = REJECTED;
                _this.value = reason;
                _this.rejectedCallbacks.forEach(cb => cb())
            }
        })
    }
    // 用于解决以下问题
    // new Promise(() => throw Error('error'))
    try {
        fn(_this.resolve, _this.reject);
    } catch(e) {
        _this.reject(e)
    }
}

MyPromise.prototype.then = function(onResolved, onRejected) {
    var self = this;
    var promise2;
    onResolved = typeof onResolved === 'function' ? onResolved : v => v;
    onRejected = typeof onRejected === 'function' ? onRejected : v => throw v;
    if(self.currentState === RESOLVED) {
        return (promise2 = new MyPromise(function(resolve, reject) {
            setTimeout(function() {
                try {
                    var x = onResolved(self.value);
                    resolutionProcedure(promise2, x, resolve, reject)
                }catch(e) {
                    console.log(e)
                }
            })
        } ))
    }
}

实现浅拷贝(shallowCopy)

// 只可以实现第一层的完全复制。
// 1. ...扩展运算符实现
let arr1 = [1, [1, 2], {name: 'shallowCopy'}];
let arr2 = [...arr1];
// 2. Object.assign()实现, [].concat, slice

实现深拷贝

function deepClone(obj) {
    // 当是RegExp对象时,重新创建
    if(obj instanceof RegExp) {
        return new RegExp(obj)
    }
    // 当是
    if(obj instanceof Date) {
        return new Date(obj)
    }
    // 当obj不是null,且非object时,即为基本数据类型,直接返回
    if( typeof obj === null || typeof obj !== 'object') {
        return obj;
    }

    let t = new obj.constructor()
    for(let key in obj) {
        if(obj.hasOwnProperty(key)) {
            t[key] = deepClone(obj[key])
        }
    }

    return t;
}

使用setTimeout模拟setinterval

// 这里没有考虑如何取消模拟的setInterval2
function setInterval2(fn, interval) {
    setInterval2.id = null;
    const func = () => {
        fn();
        setInterval2.id = setTimeout(func, interval)
    }
    func()
}
// 取消定时器
clearTimeout(setInterval2.id)

js实现一个继承方法(看下高级程序3再)

// 寄生组合式继承
// 核心实现: 用一个F空的构造函数去取代执行了Parent这个构造函数

function Parent(name) {
    this.name = name;
}
Parent.prototype.sayName = function() {
    console.log('parent name', this.name);
}

function create(proto) {
    function F() {}
    F.prototype = proto;
    return new F();
}

function Child(name) {
    this.name = name;
}
Child.prototype = create(Parent.prototype);
Child.prototype.sayName = function() {
    console.log('child name', this.name);
}
// 修改原型指向的构造函数为原来的构造函数
Child.prototype.constructor = Child;

let parent = new Parent('father');
parent.sayName();
let child = new Child('son'); 

实现一个基本的Event Bus

// 组建通信,一个触发与监听的过程
class EventEmitter {
    constructor() {
        // 存储事件
        this.events = this.events || new Map();
    }
    
    // 监听事件
    addListener(type, fn) {
        // 若该事件下没有监听器数组,则创建并放置第一个监听器
        if(type && typeof fn === 'function') {
            if(!this.events.has(type)) {
                this.events.set(type, new Set());
            }
            this.events.get(type).add(fn)
        }
    }
    
    // 缺少removeListener.想法是将set改为数组,通过遍历监听器名字来remove,但是在添加时也要遍历监听器名字,防止同一监听器被添加了两次
    
    // 触发事件
    emit(type) {
        if(this.events.has(type)) {
            for (let func of this.events.get(type).keys()) {
                func(...arguments);
            }
        }
    }
}

实现一个双向数据绑定(后续利用proxy实现)

let obj = {};
let input = document.querySelector('#input');
let span = document.querySelector('#span');
// 数据劫持
Object.defineProperty(obj, 'text', {
    configurable: true,
    enumerable: true,
    get() {
        console.log('获取数据');
        return obj['text']
    },
    set(value) {
        console.log('数据更新');
        input.value = value;
        span.innerHTML = value;
    }
});

// 输入监听
input.addEventListener('keyup', function(e) {
    // 当obj.text值发生更新时,是由set操作的,届时会更新input和span的值
    obj.text = e.target.value
})

实现懒加载

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>懒加载</title>
</head>

<body>
    <ul>
        <li><img src="./images/Image 1.png" data="./images/Image 1.png" alt=""></li>
        <li><img src="./images/Image 1.png" data="./images/Image 2.png" alt=""></li>
        <li><img src="./images/Image 1.png" data="./images/Image 3.png" alt=""></li>
        <li><img src="./images/Image 1.png" data="./images/Image 4.png" alt=""></li>
        <li><img src="./images/Image 1.png" data="./images/Image 5.png" alt=""></li>
        <li><img src="./images/Image 1.png" data="./images/Image 6.png" alt=""></li>
        <li><img src="./images/Image 1.png" data="./images/Image 7.png" alt=""></li>
        <li><img src="./images/Image 1.png" data="./images/Image 8.png" alt=""></li>
        <li><img src="./images/Image 1.png" data="./images/Image 9.png" alt=""></li>
        <li><img src="./images/Image 1.png" data="./images/Image 10.png" alt=""></li>
    </ul>
    <script>
        let imgs = document.querySelectorAll('img')
        // 可视区高度
        let clientHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight
        function lazyLoad() {
            // 滚动卷去的高度
            let scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop
            for (let i = 0; i < imgs.length; i++) {
                // 得到图片顶部距离可视区顶部的距离
                let x = clientHeight + scrollTop - imgs[i].offsetTop
                // 图片在可视区内
                if (x > 0 && x < clientHeight + imgs[i].height) {
                    imgs[i].src = imgs[i].getAttribute('data')
                }
            }
        }
        addEventListener('scroll', lazyLoad);
    </script>
</body>

</html>

rem基本设置

// 原始配置
function setRem () {
  let doc = document.documentElement
  let width = doc.getBoundingClientRect().width
  // 假设设计稿为宽750,则1rem代表10px
  let rem = width / 75
  doc.style.fontSize = rem + 'px'
}
// 监听窗口变化
addEventListener("resize", setRem)

AJAX

// 简单流程
// 实例化
let xhr = new XMLHttpRequest();

// 初始化
xhr.open(method, url, async);

// 发送请求
xhr.send(data); // get请求时 data为空

// 设置状态变化回调处理请求结果
xhr.onreadystatechange = () => {
    if(xhr.readyStatus === 4 && xhr.status === 200) {
        console.log(xhr.responseText);
    }
}

// 基于promise实现
function ajax(options = {}) {
    // 请求地址
    const url = options.url;
    // 请求方法
    const method = options.method && options.method.toLocaleLowerCase() || 'get';
    // 默认为异步true
    const async = options.async || true;
    // 请求参数
    const data = options.data;
    // 实例化
    const xhr = new XMLHttpRequest();
    // 请求超时时长
    if( options.timeout > 0) {
        xhr.timeout = options.timeout;
    }
    
    // 返回一个Promise实例
    return new Promise((resolve, reject) => {
        let paramArr = [];
        let encodeData;
        // 处理请求参数
        if(data instanceof Object) {
            for(let key in data) {
                // 参数拼接需要通过encodeURIComponent进行编码
                paramArr.push(encodeURIComponent(key) + '=' + encodeURIComponent(data[key]))
            }
            encodeData = paramArr.join('&');
        }
        // get请求拼接参数
        if(method === 'get') {
            // 检测url中是否已存在?极其位置
            const index = url.indexOf('?');
            if(index === -1) {
                url += '?'
            } else if(index !== url.length - 1) {
                url += '&'
            }
            
            url += encodeData;
        }
        // 初始化
        xhr.open(method, url, async);
        //发送请求
        if(method === 'get') {
            xhr.send();
        } else {
            // post方式需要设置请求头
            xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded;charset=UTF-8');
            xhr.send(encodeData);
        }
        
        xhr.ontimeout = () => {
            reject('请求超时');
        };
        // 监听状态变化回调
        xhr.onreadystatechange = () => {
            if(xhr.readyState === 4) {
                // 200-300 之间表示请求成功,304资源未变,取缓存
                if(xhr.status >=200 && xhr.status < 300 || xhr.status === 304) {
                    resolve(xhr.responseText);    
                } else {
                    reject();
                }
            }
        }
        // 错误回调
        xhr.onerror = err => reject(err);
    });
}

防抖

//当一次事件发生后,事件处理器要等一定阈值的时间,如果这段时间过去后 再也没有 事件发生,就处理最后一次发生的事件
//。假设还差 0.01 秒就到达指定时间,这时又来了一个事件,那么之前的等待作废,需要重新再等待指定时间。
function debounce(fn, wait, immediate) {
    let timer;
    return function() {
        if(immediate) {
            fn.apply(this, arguments)
        }
        if(timer) {
            clearTimeout(timer)
        }
        timer = setTimeout(() => {
            fn.apply(this, arguments)
        }, wait)
    }
}

节流

//在规定时间内只触发一次
function throttle (fn, delay) {
  // 利用闭包保存时间
  let prev = Date.now()
  return function () {
    let now = Date.now()
    if (now - prev >= delay) {
      fn.apply(this, arguments)
      prev = Date.now()
    }
  }
}