如何用JS封装一个库

86 阅读1分钟

如何判断一个对象是否是数组:它是否拥有数组的共有属性(push、pop等

闭包的作用

  • 一个函数引用了其外部的自由变量,这就是闭包

解构赋值

ES6语法,通过匹配进行变量声明,可以将数组中的值或者对象的属性取出,赋值给其它变量

常见用法:

js封装API

实现 jQuery 中的 addClass,有三种写法

    <div class="red"></div>
    <div class="red"></div>
    <div class="red"></div>
    <div class="red"></div>
    <div class="red"></div>
    <div class="red"></div>

假设函数中有几个相同class的 div

  • 用闭包方式封装:
    function jQuery(selector){
        const labelFakeArray = document.querySelectorAll(selector)			//把传进来的对象变成一个伪数组
        const labelArray = Array.from(labelFakeArray)										//把伪数组变成一个真数组
        const api = {
            addClass(className){
                labelArray.forEach($node => {
                    $node.classList.add(className)
                })
            },
            removeClass(className){
                labelArray.forEach($node => {
                    $node.classList.remove(className)
                })
            }
        }
        return api					//调用函数,返回可操作元素的api
    }
  	const $ = jQuery				//把函数赋值给$
  	$('red').addClass('green')			//使用函数给元素添加 class 'green'

缺点是每次调用都要再新const一个 api,耗内存

  • 用构造函数方法封装
    function jQuery(selector){
        const fake_labelArray = document.querySelectorAll(selector)
        this.labelArray = Array.from(fake_labelArray)
    }
    jQuery.prototype = {
        constructor: jQuery,
        addClass(className){
            this.labelArray.forEach($node => {
                $node.classList.add(className)
            })
        },
        removeClass(className){
            this.labelArray.forEach($node => {
                $node.classList.remove(className)
            })
        }
    }
  	const $ = jQuery
  	new $('red').addClass('green')				//每次调用都要 new 一下
  • 用 class 方法封装
    class jQuery{
        constructor(selector){
            this.fake_labelArray = document.querySelectorAll(selector)
            this.labelArray = Array.from(this.fake_labelArray)
        }
        addClass(className){
            const { labelArray } = this
            labelArray.forEach($node => {
                $node.classList.add(className)
            })
        }
        removeClass(className){
            this.labelArray.forEach($node => {
                $node.classList.remove(className)
            })
        }
    }
  	const $ = jQuery
    new $('red').addClass('green')

class 和 构造函数方法都要新 new 一下

  • 在构造函数方法中如何不用 new 也可以调用api
    function jQuery(selector){
        if(!(this instanceof jQuery)){				//在前置中添加一个判断,如果 this 不属于jQuery,则帮用户返回 new
            return new jQuery(selector)
        }
        const fake_labelArray = document.querySelectorAll(selector)
        this.labelArray = Array.from(fake_labelArray)
    }
    jQuery.prototype = {
        constructor: jQuery,
        addClass(className){
            this.labelArray.forEach($node => {
                $node.classList.add(className)
            })
        },
        removeClass(className){
            this.labelArray.forEach($node => {
                $node.classList.remove(className)
            })
        }
    }

链式调用

连续执行两个或多个操作

以上面封装的api为例,在给div添加class green 之后,再添加一个 blue

可以通过给 api 添加 this,再链式调用来实现

        addClass(className){
            this.labelArray.forEach($node => {
                $node.classList.add(className)
            })
          	return this
        },
        removeClass(className){
            this.labelArray.forEach($node => {
                $node.classList.remove(className)
            })
            return this
        }
  • 如何确定 this 的值?
  1. 查看 MDN 文档
  2. 查看浏览器源码
  3. 查看函数调用 (不能看函数声明)

函数的五种调用方式

  1. fn (参数)

//这个方法的this是window,严格模式下是undefined

  1. obj.method(参数)

//this是method前面的对象,这里是obj,如果是obj.x.methos,则this是obj.x

  1. fn.call(this, 参数1) //给函数指定 this
  2. fn.apply(this, [参数1]) //给函数指定 this
  3. new Fn(参数) //new会在调用过程中把this指向新构建的对象

call 和 apply 会把传进来的 this 转化为对象

  • 浏览器全局作用域中,非严格模式下

this = window,name = ""

箭头函数不支持 this

  • 箭头函数不支持 this,箭头函数中的 this 是 window
  • 在箭头函数中,this 和 a, b, c 这样的普通变量一样
  • 即使用 call 给箭头函数传值,也不支持this
  • 箭头函数不支持用 new 调用

函数重载

即同名不同参,接受不同数据类型的参数

(.red)接受字符串,('.red') 接受字符串,(redList) 接受元素数组(伪),$(red)接受单个元素

只需要学会使用 if else 和 instanceof

        function jQuery(selector) {
            if (!(this instanceof jQuery)) {
                return new jQuery(selector)
            }
            if (typeof selector === 'string') {			//判断参数是否属于 string
                const fake_labelArray = document.querySelectorAll(selector)
                this.labelArray = Array.from(fake_labelArray)
            }else if(selector instanceof Element) { //判断参数是否属于元素
                this.labelArray = [selector]
            }else {																	//上面都不属于,则是元素伪数组
                this.labelArray = Array.from(selector)
            }
        }

如何接受不同长度的参数

如,(.red)在整个页面查找.red('.red') 在整个页面查找 .red,('.red', div) 在某个 div 里查找 .red

用 if else,配合 arguments.length 实现

		<div class="red"></div>
    <div class="red" id="ccc">
        <div class="red"></div>
        <div class="red"></div>
        <div class="red"></div>
    </div>
    <div class="red"></div>
    <div class="red"></div>
<script>
        function jQuery(selector, range) {
            console.log(jQuery.length);
            if (!(this instanceof jQuery)) {
                return new jQuery(selector, range)
            }
            let fake_labelArray = arguments.length === 2 ? 	//判断是否传入了两个参数
            range.querySelectorAll(selector) : 							//是,则在range内选取selector
            document.querySelectorAll(selector)							//不是,则在页面选取selector
            this.labelArray = Array.from(fake_labelArray)
        }
        let ccc = document.querySelector('#ccc')
        $('.red', ccc).addClass('gu')
</script>
  • arguments 是函数实际传入的参数,上面代码用 arguments.length 判断是否传入了两个参数,如果是则在传入的 range 内选取 selector,不是则在全局范围内选取 selector
  • 也可以通过 range 是否为 undefined 来判断
            let fake_labelArray = range === undefined ? 
            range.querySelectorAll(selector) : 							
            document.querySelectorAll(selector)		
  • argument是函数传入的实参,
  • jQuery(函数名).length 则为函数的形参长度