我是靠谱客的博主 干净睫毛,最近开发中收集的这篇文章主要介绍JS常见方法实现,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

1.new的实现

new Foo(...)

new一个对象就是创建一个新对象,这个新对象既拥有Foo的constructor里的属性,又继承了Foo.prototype,并返回这个新对象。

步骤如下:

1、创建一个空对象;

2、让空对象的__proto__(IE没有该属性)成员指向了构造函数的prototype成员对象

3、使用apply调用构造器函数,属性和方法被添加到 this 引用的对象中

4、如果构造函数中没有返回其它对象,那么返回 this,即创建的这个的新对象,否则,返回构造函数中返回的对象

function _new() {
    const obj= {}; // 创建的新对象
    // arguments伪数组的第一个参数就是构造函数,因此用shift取出第一个参数,[]等价于Array.prototype
    const constructor = [].shift.call(arguments);
    const args = [...arguments].slice(1);   //获取argument除了第一个以外的参数
    // 执行 [[原型]] 连接 ;实际上就是生产了一个新的上下文
    obj.__proto__ = constructor.prototype;
    // 使用apply在obj作用域中调用构造器函数,属性和方法被添加到 this 引用的对象即obj中
    const result = constructor.apply(obj, args);
   //如果构造函数中没有返回其它对象,那么返回 this,即创建的这个的新对象,否则,返回构造函数中返回的对象
    return typeof result === 'object' ? result : obj;
}

 

2.call、apply、bind的实现。

2.1 call的实现

 

完整代码如下:

Function.prototype._call = function(thisArg) {
  
  if (typeofthis !== 'function') {   // this指向调用call的对象
    thrownewTypeError('Error');      // 调用call的若不是函数则报错
  }
 
  const args = [...arguments].slice(1);   //获取argument除了第一个以外的参数
 
  thisArg = thisArg || window;   //考虑当thisArg为null的情况,此时实际_call接受的参数是window
  
  // 将调用call函数的对象添加到thisArg的属性中
  //这里的this指的是调用_call()的对象,而fn是临时起的中间变量(名字随意),临时使用后在后面会删除
  thisArg.fn = this;  
  // 执行该属性
  const result = thisArg.fn(...arg);
  // 删除该属性
  delete thisArg.fn;
  // 返回函数执行结果
  return result;
};

2.2 apply的实现

apply的实现与call类似:

Function.prototype.myapply = function(thisArg) {
  if (typeofthis !== 'function') {
    thrownewTypeError('Error');
  }

  const args = arguments[1]; //获取arguments的第二个参数,即apply获取的数组参数

  thisArg.fn = this;

  const result = thisArg.fn(...arg);

  delete thisArg.fn;

  return result;
};

2.3 bind的实现

Function.prototype._bind = function () {
    // 思路:bind函数实际上每次返回的是一个函数
    // bind 函数的第一个参数是this的指向,其他参数是传递的参数
    // 1. 接受参数,解析this和args
    var self = this;       //原函数
    var context = [].shift.call(arguments);  //arguments首元素,即_bind()括号里绑定的函数
    var args = [].slice.call(arguments, 1);     //接受参数,获取arguments剩余参数转成数组
    return function(){                       // 处理用户传递的参数,arguments 就是用户传递给我的函数参数
    // 修改this的指向, 用concat方法接收多个参数组成的数组
        self.apply(context, args.concat([].slice.call(arguments)))
    }
 }

 

call() 方法在使用一个指定的 this 值和若干个指定的参数值的前提下调用某个函数或方法。

举个例子:

var foo = { value: 1 }; function bar() { console.log(this.value); } bar.call(foo); // 1

注意两点:

  • 1.call 改变了 this 的指向,指向到 foo
  • 2.bar 函数执行了

我们该怎么模拟实现这两个效果呢?

试想当调用 call 的时候,把 foo 对象改造成如下:

var foo = { value: 1, bar: function() { console.log(this.value) } }; foo.bar(); // 1

这个时候 this 就指向了 foo,是不是很简单呢?

但是这样却给 foo 对象本身添加了一个属性,这可不行呐!

不过也不用担心,我们用 delete 再删除它不就好了~

所以我们模拟的步骤可以分为:

  • 将函数设为对象的属性
  • 执行该函数
  • 删除该函数

以上个例子为例,就是:

第一步 foo.fn = bar 第二步 foo.fn() 第三步 delete foo.fn

fn 是对象的属性名,反正最后也要删除它,所以起成什么都无所谓。

3. Ajax的实现

1、原生ajax的实现

function ajax(method, url, async) {   
            var xhr = new XMLHttpRequest();
            xhr.onreadystatechange = function() {
                if(xhr.readyState == 4) {
                    if(xhr.status >= 200 && xhr.status < 300) {
                        console.log(xhr.responseText)
                    }else if(xhr.status >= 400) {
                        console.log("请求失败")
                    }
                }
            }      
            xhr.open(method, url, async);     
            xhr.send(null);
        }

 

2、满足promise的jQuery.ajax的实现:

window.jQuery.ajax = function({url, method}) { // 传参是 es6解构赋值
        return new Promise ( function(resolve, reject){ // 这一行很关键
                let xhr = new XMLHttpRequest()
                xhr.open(method, url)
                xhr.onreadystatechange = ()=>{
                        if(xhr.readyState === 4) {
                                if(xhr.status >= 200 && xhr.status < 300) {
                                        resolve.call(undefined, xhr.responseText)
                                } else if (xhr.status >= 400) {
                                        reject.call(undefined, xhr)
                                }
                        }
                }
                xhr.send()
        })
}

 

4. promise的实现

https://www.jianshu.com/p/c633a22f9e8c

4.1 Promise代码基本结构

实例化Promise对象时传入一个函数作为执行器,有两个参数(resolve和reject)分别将结果变为成功态和失败态。当实例化Promise时,构造函数中就要马上调用传入的executor函数执行。我们可以写出基本结构:

function Promise(executor) {
    var _this = this;
    this.state = 'pending'; //状态
    this.value = undefined; //成功结果
    this.reason = undefined; //失败原因
    
    executor(resolve, reject); //马上执行
    
    function resolve(value) { }

    function reject(reason) { }
}

module.exports = Promise;

 

其中state属性保存了Promise对象的状态,规范中指明,一个Promise对象只有三种状态:等待态(pending)成功态(resolved)和失败态(rejected)。

当一个Promise对象执行成功了要有一个结果,它使用value属性保存;也有可能由于某种原因失败了,这个失败原因放在reason属性中保存。

 

已经是成功态或是失败态不可再更新状态:当Promise对象已经由pending状态改变为了成功态(resolved)或是失败态(rejected)就不能再次更改状态了。因此我们在更新状态时要判断,如果当前状态是pending(等待态)才可更新:

function resolve(value) {
        //当状态为pending时再做更新
        if (_this.state === 'pending') {
            _this.value = value;//保存成功结果
            _this.state = 'resolved';
        }

    }

    function reject(reason) {
    //当状态为pending时再做更新
        if (_this.state === 'pending') {
            _this.reason = reason;//保存失败原因
            _this.state = 'rejected';
        }
    }

 

then方法:每一个Promise实例都有一个then方法,它用来处理异步返回的结果,它是定义在原型上的方法:

Promise.prototype.then = function (onFulfilled, onRejected) {
    if (this.state === 'resolved') {
        //判断参数类型,是函数执行之
        if (typeof onFulfilled === 'function') {
            onFulfilled(this.value);
        }

    }
    if (this.state === 'rejected') {
        if (typeof onRejected === 'function') {
            onRejected(this.reason);
        }
    }
};

 

让promise支持异步:

我们可以参照发布订阅模式,在执行then方法时如果还在等待态(pending),就把回调函数临时寄存到一个数组里,当状态发生改变时依次从数组中取出执行就好了,清楚这个思路我们实现它,首先在类上新增两个Array类型的数组,用于存放回调函数:

function Promise(executor) {
    var _this = this;
    this.state = 'pending';
    this.value = undefined;
    this.reason = undefined;
    this.onFulfilledFunc = [];//保存成功回调
    this.onRejectedFunc = [];//保存失败回调
    //其它代码略...
}

 

这样当then方法执行时,若状态还在等待态(pending),将回调函数依次放入数组中:

Promise.prototype.then = function (onFulfilled, onRejected) {
    //等待态,此时异步代码还没有走完
    if (this.state === 'pending') {
        if (typeof onFulfilled === 'function') {
            this.onFulfilledFunc.push(onFulfilled);//保存回调
        }
        if (typeof onRejected === 'function') {
            this.onRejectedFunc.push(onRejected);//保存回调
        }
    }
    //其它代码略...
};

 

寄存好了回调,接下来就是当状态改变时执行就好了:

function resolve(value) {
        if (_this.state === 'pending') {
            _this.value = value;
            //依次执行成功回调
            _this.onFulfilledFunc.forEach(fn => fn(value));
            _this.state = 'resolved';
        }

    }

    function reject(reason) {
        if (_this.state === 'pending') {
            _this.reason = reason;
            //依次执行失败回调
            _this.onRejectedFunc.forEach(fn => fn(reason));
            _this.state = 'rejected';
        }
    }

 

使promise支持链式调用:

/** * 解析then返回值与新Promise对象 
* @param {Object} promise2 新的Promise对象 
* @param {*} x 上一个then的返回值 
* @param {Function} resolve promise2的resolve 
* @param {Function} reject promise2的reject 
*/
function resolvePromise(promise2, x, resolve, reject) {
    if (promise2 === x) {
        reject(new TypeError('Promise发生了循环引用'));
    }
   if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
    //可能是个对象或是函数
    try {
        let then = x.then; 
        if (typeof then === 'function') {
            let y = then.call(x, (y) => {
                //递归调用,传入y若是Promise对象,继续循环
                resolvePromise(promise2, y, resolve, reject);
            }, (r) => {
                reject(r);
            });
        } else {
            resolve(x);
        }
    } catch (e) {
        reject(e);
    }

} else {
    //是个普通值,最终结束递归
    resolve(x);
}

 

完整代码如下:

function Promise(executor) {
    let self = this
    this.status = 'pending' //当前状态
    this.value = undefined  //存储成功的值
    this.reason = undefined //存储失败的原因
    this.onResolvedCallbacks = []//存储成功的回调
    this.onRejectedCallbacks = []//存储失败的回调
    function resolve(value) {
        if (self.status == 'pending') {
            self.status = 'resolved'
            self.value = value
            self.onResolvedCallbacks.forEach(fn => fn());
        }
    }
    function reject(error) {
        if (self.status == 'pending') {
            self.status = 'rejected'
            self.reason = error
            self.onRejectedCallbacks.forEach(fn => fn())
        }
    }
    try {
        executor(resolve, reject)
    } catch (error) {
        reject(error)
    }
}
Promise.prototype.then = function (infulfilled, inrejected) {
    let self = this
    let promise2
    infulfilled = typeof infulfilled === 'function' ? infulfilled : function (val) {
        return val
    }
    inrejected = typeof inrejected === 'function' ? inrejected : function (err) {
        throw err
    }
    if (this.status == 'resolved') {
        promise2 = new Promise(function (resolve, reject) {
            //x可能是一个promise,也可能是个普通值
            setTimeout(function () {
                try {
                    let x = infulfilled(self.value)
                    resolvePromise(promise2, x, resolve, reject)
                } catch (err) {
                    reject(err)
                }
            });

        })
    }
    if (this.status == 'rejected') {

        promise2 = new Promise(function (resolve, reject) {
            //x可能是一个promise,也可能是个普通值
            setTimeout(function () {
                try {
                    let x = inrejected(self.reason)
                    resolvePromise(promise2, x, resolve, reject)
                } catch (err) {
                    reject(err)
                }
            });
        })
    }
    if (this.status == 'pending') {
        promise2 = new Promise(function (resolve, reject) {
            self.onResolvedCallbacks.push(function () {
                //x可能是一个promise,也可能是个普通值
                setTimeout(function () {
                    try {
                        let x = infulfilled(self.value)
                        resolvePromise(promise2, x, resolve, reject)
                    } catch (err) {
                        reject(err)
                    }
                });
            })
            self.onRejectedCallbacks.push(function () {
                //x可能是一个promise,也可能是个普通值
                setTimeout(function () {
                    try {
                        let x = inrejected(self.reason)
                        resolvePromise(promise2, x, resolve, reject)
                    } catch (err) {
                        reject(err)
                    }
                });
            })
        })
    }
    return promise2
}

//处理链式调用问题
function resolvePromise(p2, x, resolve, reject) {
    if (p2 === x && x != undefined) {
        reject(new TypeError('类型错误'))
    }
    //可能是promise,看下对象中是否有then方法,如果有~那就是个promise
    if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
        try {//为了防止出现 {then:11}这种情况,需要判断then是不是一个函数
            let then = x.then
            if (typeof then === 'function') {
                then.call(x, function (y) {
                    //y 可能还是一个promise,那就再去解析,知道返回一个普通值为止
                    resolvePromise(p2, y, resolve, reject)
                }, function (err) {
                    reject(err)
                })
            } else {//如果then不是function 那可能是对象或常量
                resolve(x)
            }
        } catch (e) {
            reject(e)
        }
    } else {//说明是一个普通值
        resolve(x)
    }
}

5.闭包的应用

创建10个a标签,点击的时候,弹出对应的序号:

<script>
        for (var i = 0; i < 10; i++) {
            (function (i) {
                var a = document.createElement('a');
                a.innerHTML = i + '<br>';
                document.body.appendChild(a);//继续放置a标签
                a.addEventListener('click', function (e) {
                    e.preventDefault();  //取消默认事件,指a标签
                    alert(i);
                });
            })(i);
        }
 </script>

 

 

6.浅拷贝和深拷贝

浅拷贝:

浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。

浅拷贝方法:Object.assign()、Array.prototype.concat()、Array.prototype.slice()、ES6的展开运算符"..."

赋值 & 浅拷贝 & 深拷贝的区别:

 

 

 

深拷贝:

深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。

深拷贝简单做法:JSON.parse(JSON.stringfy(obj)) 但是该方法也是有局限性的:

  • 会忽略undefined
  • 会忽略symbol
  • 会忽略函数
  • 不能解决循环引用的对象 (会报错)

自封装深拷贝思路:

  • 使用for-in遍历对象
  • 因为for-in会遍历原型链上的属性,所以需要判断属性是否在原型链上,不是原型链才拷贝
  • 判断属性值类型是原始类型和引用类型
  • 原始类型直接赋值(注意null)
  • 引用类型判断是对象还是数组,创建对应的空对象或空数组,递归调用函数,将值赋值进去
function deepClone(target) {
    // 定义一个变量
    let result;
    // 如果当前需要深拷贝的是一个对象的话
    if (typeof target === 'object') {
    // 如果是一个数组的话
        if (Array.isArray(target)) {
            result = []; // 将result赋值为一个数组,并且执行遍历
            for (let i in target) {
                // 递归克隆数组中的每一项
                result.push(deepClone(target[i]))
            }
         // 判断如果当前的值是null的话;直接赋值为null
        } else if(target===null) {
            result = null;
         // 判断如果当前的值是一个RegExp对象的话,直接赋值    
        } else if(target.constructor===RegExp){
            result = target;
        }else {
         // 否则是普通对象,直接for in循环,递归赋值对象的所有值
            result = {};
            for (let i in target) {
                result[i] = deepClone(target[i]);
            }
        }
     // 如果不是对象的话,就是基本数据类型,那么直接赋值
    } else {
        result = target;
    }
     // 返回最终结果
    return result;
}

 

7. 防抖节流实现

节流与防抖的本质:以闭包的形式存在,通过对事件对应的回调函数进行包裹、以自由变量的形式缓存时间信息,最后用定时器或时间差来控制事件的触发频率。

1、防抖

所谓防抖,就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。

典型应用:

  • 百度搜索框在输入稍有停顿时才更新推荐热词。
  • 拖拽
function debounce(handler, delay){

  delay = delay || 300;
  var timer = null;

  return function(){

    var _self = this,
        _args = arguments;

    clearTimeout(timer);
    timer = setTimeout(function(){
      handler.applay(_self, _args);
    }, delay);
  }
}

 

2、节流

所谓节流,就是指连续触发事件但是在 n 秒中只执行一次函数。节流会稀释函数的执行频率,好像水滴积攒到一定程度才会触发一次下落一样。

对于节流,一般有两种方式可以实现,分别是时间戳版和定时器版。

典型应用:

  • 抢券时疯狂点击,既要限制次数,又要保证先点先发出请求
  • 窗口调整
  • 页面滚动
//定时器法:
function throttle(func, wait) {
    let timeout;
    return function() {
        let context = this;
        let args = arguments;
        if (!timeout) {
            timeout = setTimeout(() => {
                timeout = null;
                func.apply(context, args)
            }, wait)
        }

    }
}

//时间戳版
function throttle(handler, wait) {
wait = wait || 300;
var lastTime = 0;
return function(){
    var _self = this,
    _args = arguments;

   var nowTime = new Date().getTime();
   if((nowTime - lastTime) > wait){
       handler.apply(_self, _args);
       lastTime = nowTime;
   }
 }
}

 

8. 观察者模式

观察者模式:观察者模式(Observer mode)指的是函数自动观察数据对象,一旦对象有变化,函数就会自动执行。而js中最常见的观察者模式就是事件触发机制。

add:添加事件某种类型的函数,

remove: 移除某种类型的函数,

fire:触发某种类型的函数,

once:触发某种类型的函数,然后移除掉这个函数

 

//ES6实现
class eventObs {
    
    //handler:触发函数,详情如下(其中type表示触发事件类型,handle表示触发后执行的函数):
    //handler={
    //    type1:[func1,func2...],
    //    type2:[func2,func4...],
    //    ...
    //}
       
    constructor(){
        this.handleFunc={}
    }
    
    //add:添加一个事件监听,传入参数应该是事件类型type和触发函数func,传入的时候检测有没有这个函数,有了就不重复添加。
    add(type,func){
        if(this.handleFunc[type]){
            if(this.handleFunc[type].indexOf(func)===-1){
                this.handleFunc[type].push(func);
            }
        }else{
            this.handleFunc[type]=[func];
        }

    };
    //  fire:触发某种类型的函数,触发一个点击事件肯定是要触发它全部的函数,这里也是一样,所以只需要传入type,
    //特殊处理:事件可能不存在,像下面remove一样处理。
    fire(type,func){
        try{

            if(arguments.length===1) {
                let target = this.handleFunc[type];
                let count = target.length;
                for (var i = 0; i < count; i++) {
                    target[i]();
                }
            }else{
                let target = this.handleFunc[type];
                let index=target.indexOf(func);
                if(index===-1)throw error;
                func();
            }
            return true;
        }catch (e){
            console.error('别老想搞什么飞机,触发我有的东西!');
            return false;
        }
    };
    
    //remove:移除某种类型的函数,该函数有个潜在需求,就是如果你的事件不存在,它应该会报错。
    //而这里不会报错,index在func不存在的时候是-1;这时候要报错。
    remove(type,func){
        try{
            let target = this.handleFunc[type];
            let index=target.indexOf(func);
            if(index===-1)throw error;
            target.splice(index,1);
        }catch (e){
            console.error('别老想搞什么飞机,删除我有的东西!');
        }

    };
    //fire,然后remove?
    //但会有问题,我只想触发并且删除某个事件怎么办,fire一下就全触发了呀。 
    //所以fire的问题就显现出来了。我们还是要给它一个func,但是可选,即once函数。
    once(type,func) {

        this.fire(type, func)?
            this.remove(type, func):null;

    }

}

 

9. DOM树遍历

// 深度遍历
function interator(node) {
    console.log(node);
    if (node.children.length) {  //该节点有后代,则遍历该节点后代所有节点
        for (var i = 0; i < node.children.length; i++) {
            interator(node.children[i]);   
        }
    }
}

// 广度遍历
function interator(node) {

    var arr = [];
    arr.push(node);   //存入根节点
    while (arr.length > 0) {
        node = arr.shift(); //弹出arr中的首个节点
        console.log(node);
        if (node.children.length) {  //该层节点遍历完了,则遍历下一层子节点
            for (var i = 0; i < node.children.length; i++) {
                arr.push(node.children[i]);
            }
        }
    }
}

 

10. 数据类型判断

/**
 * 类型判断
 */
function getType(target) {
  //先处理最特殊的Null
  if(target === null) {
    return 'null';
  }
  
  const typeOfT = typeof target;
  
  //判断是不是基础类型
  if(typeOfT !== 'object') {
    return typeOfT;
  }
  //肯定是引用类型了
  const template = {
    "[object Object]": "object",
    "[object Array]" : "array",
    "[object Function]": "function",
    // 一些包装类型
    "[object String]": "object - string",
    "[object Number]": "object - number",
    "[object Boolean]": "object - boolean"
  };
  const typeStr = Object.prototype.toString.call(target);
  return template[typeStr];
}

 

最后

以上就是干净睫毛为你收集整理的JS常见方法实现的全部内容,希望文章能够帮你解决JS常见方法实现所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(58)

评论列表共有 0 条评论

立即
投稿
返回
顶部