珠峰web高级7期(二)

面向对象

构造函数 VS 普通函数

/*
 * 编程语言
 *    面向对象 OOP  java、javascript、php、C#(ASP.NET)、Python、GO、C++、Ruby...
 *    面向过程 POP  C
 * 标记语言:HTML / CSS 
 * 
 * =====面向对象编程「对象、类、实例」
 *   对象:万物皆对象(泛指)
 *   类:对“对象”的划分(按照其功能结构特点,划分出大类和小类)
 *   实例:类中具体的事务
 * 
 * JS本身就是基于面向对象思想开发出来的编程语言,所以我们学习和开发JS的时候,也要按照面向对象的思想去处理!!
 *  「内置类」
 *     + 每一种数据类型都有一个自己所属的内置类:Number数字类(每一个数字/NaN/Infinity都是它的实例)、String、Boolean、Symbol、BigInt、Array、RegExp、Date、Function、Object...
 *     + 每一种DOM元素也都有自己所属的类:
 *       window -> Window -> WindowProperties -> EventTarget -> Object
 *       document -> HTMLDocument -> Document -> Node -> EventTarget -> Object
 *       div -> HTMLDivElement -> HTMLElement -> Element -> Node -> ...
 *       a -> HTMLAnchorElement -> HTMLElement -> ...
 *     + HTMLCollection / NodeList / CSSStyleDeclaration /  DOMTokenList ...
 *     + ....
 *   学习数组,首先分析一个数组(实例),研究清楚这个实例的特征后(含:结构特点和常用方法等),我们再遇到其他的数组,直接也是按照相同的机制进行处理的
 *   
 *  「自定义类」
 *     创建一个函数 fn
 *       + fn() 普通函数执行「堆栈机制」
 *       + new fn() 构造函数执行 「堆栈机制 + 面向对象机制」
 */
function Fn(x, y) {
    let total = x + y;
    this.x = x;
    this.y = y;
}
let result = new Fn(10, 20);
console.log(result);
复制代码

image.png

对象遍历问题及解决方案

function Fn() {
    /*
     * EC(FN)
     *   初始创建Fn找个类的一个实例对象  0x000
     *   初始THIS:this->0x000
     */
    let total = 0; //上下文的私有变量  和实例对象没有必然的联系
    this.x = 10; //this.xxx=xxx 都是给实例对象设置的私有属性和方法
    this.y = 20;
    this.say = function () { //0x000.say=0x100   0x001.say=0x101
        console.log('SAY');
    };
    /* 如果不设置返回值,或者返回值是一个基本类型值,默认都会把实例对象 0x000 返回;如果手动返回的是一个引用数据类型值,则以自己返回的为主; */
    // return {
    //     name: 'zhufeng'
    // };
}
let f1 = new Fn(); //->0x000
let f2 = new Fn; //->0x001  new执行的时候,如果类不需要传递实参,可以不用加小括号(不加小括号,叫做无参数列表new;设置小括号,叫做带参数列表new;除了是否传递参数的区别,在运算的优先级上也有区别? new Fn->19  new Fn()->20)
// 每一次new都是把函数重新执行(重新形成一个新的私有上下文、重新创建一个实例对象、代码重新执行...)
// console.log(f1, f2, f1 === f2); //=>false

// 检测某个成员(属性/键)是否属于这个对象,或者是否属于这个对象的私有属性
// in:检测成员是否属于这个对象「特点:不论是私有属性,还是公有的属性,只要有则检测结果就是true」
// hasOwnProperty:用来检测当前成员是否为对象的私有属性「特点:只有是私有属性,结果才是ture,哪怕有这个属性,但是属于公有的属性,结果也是false」

// console.log(f1);
// console.log('say' in f1); //->true
// console.log(f1.hasOwnProperty('say')); //->true
// f1是一个对象,他可以访问hasOwnProperty方法并且执行,说明:‘hasOwnProperty’属性是它的一个成员
// console.log('hasOwnProperty' in f1); //->true
// console.log(f1.hasOwnProperty('hasOwnProperty')); //->false 说明‘hasOwnProperty’不是它的私有属性,也就是它的公有属性「前提基于in检测出来的结果是true」

// obj:要检测的对象
// attr:要验证的成员
 function hasPubProperty(obj, attr) {
    // 思路一:是它的属性 但是还不是私有的,那么一定是公有的「BUG:如果某个属性即是私有的,也是公有的,则检测出来的结果是不准确的」
    // return (attr in obj) && (!obj.hasOwnProperty(attr));
    
    // 思路二:真正的思路应该是检测原型上的属性,因为原型上的属性都是公有的
    // Object.getPrototypeOf:获取当前对象的原型
    let proto = Object.getPrototypeOf(obj);
    while (proto) {
        // 依次查找原型链,直到找到Object.prototype为止
        if (proto.hasOwnProperty(attr)) {
            return true;
        }
        proto = Object.getPrototypeOf(proto);
    }
    return false;
} 

// let sy = Symbol();
// let obj = {
//     name: 'zhufeng',
//     age: 12,
//     3: '哈哈哈',
//     0: 'zhouxiaotian',
//     [sy]: 100
// };
/* console.log(obj);
console.log(obj.hasOwnProperty('name'));
console.log(obj.hasOwnProperty(sy)); //->hasOwnProperty是可以检测Symbol属性的
console.log(sy in obj); //->in也是可以检测Symbol属性的 */

// 很多对“对象”的操作是无法拿到Symbol属性的
// Object.prototype.AAA = 100; //->‘AAA’是obj公共的属性  obj.hasOwnProperty('AAA')->false  'AAA' in obj->true

/* for (let key in obj) {
    console.log(key); //->‘name’ ‘AAA’  
    // for in遍历的时候
    //  + 无法遍历Symobol的私有属性
    //  + 但是可以遍历到自己扩展的公共属性「内置的公共属性是不可枚举的(就是无法遍历到的)」
    //  + 优先遍历数字属性,而且按照从小到大(不会严格按照属性书写的顺序)
} */

/* // 解决:能够避免遍历公共的
for (let key in obj) {
    if (!obj.hasOwnProperty(key)) break; //已经遍历到公共的,则私有已经遍历完,结束循环
    console.log(key); //->'name'
} */

/* // 解决:只想遍历私有的,包含Symbol的
// Object.keys:获取一个对象非Symbol的私有属性(结果是一个数组,数组中包含获取的属性)  
// 类似的还有:Object.getOwnPropertyNames 
// Object.getOwnPropertySymbols:只获取Symbol的私有属性(结果也是一个数组)
let keys = [
    ...Object.getOwnPropertyNames(obj),
    ...Object.getOwnPropertySymbols(obj)
];
keys.forEach(key => {
    console.log(key, obj[key]);
}); */
复制代码

原型和原型链

image.png

原型重定向

image.png

function Fn() {}
Fn.prototype.x = 100;
Fn.prototype.y = 200;

/* // 缺少constructor && 原始原型对象上的x/y也丢失了
Fn.prototype = {
    // getX:function() {}
    getX() {},
    getY() {}
}; */

/* let proto = {
    // 手动设置的constructor是属于可枚举的
    constructor: Fn,
    getX() {},
    getY() {}
};
Fn.prototype = Object.assign({}, Fn.prototype, proto); //->这样合并,最后返回的是一个全新的对象,由于内置的Fn.prototype中的constructor是内置的不可枚举的属性,所以合并后也是无法赋给新对象的 */

/* Fn.prototype = Object.assign(Fn.prototype, {
    getX() {},
    getY() {}
}); //->这种合并的办法,Fn.prototype还是之前的堆地址,只不过是把新对象中的内容全部扩展到了原始的堆中 */

/* 
let obj1 = {
    x: 100,
    y: 200,
    n: {
        0: 1,
        1: 2
    }
};
let obj2 = {
    y: 300,
    z: 400,
    n: {
        name: 'zhufeng'
    }
};
// Object.assign:合并两个对象「浅比较」
//   + 让obj2中的内容替换obj1中的:两者都有的以obj2为主,只有其中一个具备的都是相当于新增...
//   + 最后返回的是obj1对象的堆内存地址「相当于改变的是obj1对象中的内容」,并不是返回一个全新的对象...
// let obj = Object.assign(obj1, obj2);
// console.log(obj === obj1); //true

// let obj = Object.assign({}, obj1, obj2);
// console.log(obj); //->全新的对象,也就是assign的第一个参数「新对象」

// console.log(Object.assign(obj1, obj2)); //->浅比较:obj2.n直接覆盖obj1.n 
*/

/* let obj = {
    fn1() {},
    fn2: function fn2() {}
    // 两者写法的区别:
    //   + 第一种写法:obj.fn1函数是没有prototype属性的 「不能被作为构造函数」
    //   + 第二种写法:和正常的函数没有区别
};
new obj.fn1(); //Uncaught TypeError: obj.fn1 is not a constructor */
复制代码

内置类原型扩展方法

image.png

/* 
 * 向内置类的原型扩展方法
 *   + 内置类的原型上提供了很多内置方法,但是这些方法不一定完全满足业务需求,此时需要我们自己扩展一些方法
 *  「优势」
 *   + 调用起来方便
 *   + 可以实现链式写法
 *   + 限定调取方法的类型,必须是指定类的实例
 *   + 扩展的方法,各个模块「其他成员」都可以直接的调用
 *   + ...
 *  「弊端」
 *   + 自己扩展的方法,容易覆盖内置的方法 (解决:自己设定的方法名要设置前缀 myUnique)
 *     Array.prototype={...} 这样操作是无效的,也怕你一行代码,把数组方法全干没了
 *   + 基于for in遍历的时候,会把自己扩展到原型上的方法也遍历到
 *   + ...
 */
/* 
function unique(arr) {
    let obj = {};
    for (let i = 0; i < arr.length; i++) {
        let item = arr[i];
        if (obj.hasOwnProperty(item)) {
            // 数组之前出现过这一项,当前项就是重复的,我们此时删除当前项即可
            arr.splice(i, 1);
            i--;
            continue;
        }
        obj[item] = item;
    }
    return arr;
}
let arr = [10, 30, 40, 20, 40, 30, 10, 40, 20];
arr = unique(arr);
*/

Array.prototype.unique = function unique() {
    // this:一般都是当前要操作的实例(也就是要操作的数组)
    let obj = {},
        self = this;
    for (let i = 0; i < self.length; i++) {
        let item = self[i];
        if (obj.hasOwnProperty(item)) {
            // 数组之前出现过这一项,当前项就是重复的,我们此时删除当前项即可
            self.splice(i, 1);
            i--;
            continue;
        }
        obj[item] = item;
    }
    return self; //实现链式写法
};
let arr = [10, 30, 40, 20, 40, 30, 10, 40, 20];
arr.unique().sort((a, b) => a - b).reverse().push('zhufeng'); //执行完成sort返回的是排序后的数组(原始数组也是变的)... 执行完成push返回的是新增后数组的长度「不能再调数组方法了」 => “链式写法”:执行完成一个方法,返回的结果是某个实例,则可以继续调用这个实例所属类原型上的方法...
console.log(arr);
复制代码

重写内置new

/* 
function Fn() {
    // 创建一个实例对象
    // ---- 也会像普通函数执行一样,让其执行 「THIS指向实例对象」
    // 返回值没有或者是基本值,则返回的是实例对象...
}
let f1 = new Fn; 
*/


// 分析内置new的原理,重写一下
function Dog(name) {
    this.name = name;
}
Dog.prototype.bark = function () {
    console.log('wangwang');
}
Dog.prototype.sayName = function () {
    console.log('my name is ' + this.name);
}

function _new(Ctor, ...params) {
    // 1.创建一个实例对象「创建Ctor类的实例:实例.__proto__ -> 类.prototype」
    /* let obj = {};
    obj.__proto__ = Ctor.prototype; */
    let obj = Object.create(Ctor.prototype);

    // 2.把函数执行「THIS指向实例对象」  call->执行函数,改变函数中的THIS
    let result = Ctor.call(obj, ...params);

    // 3.处理返回值
    if (result !== null && /^(object|function)$/.test(typeof result)) return result;
    return obj;
}
let sanmao = _new(Dog, '三毛');
sanmao.bark(); //=>"wangwang"
sanmao.sayName(); //=>"my name is 三毛"
console.log(sanmao instanceof Dog); //=>true

// ----https://www.caniuse.com/
// Object.create([proto]):创建一个空对象,并且让创建的这个空对象的.__proto__指向[proto]  “把[proto]作为创建对象的原型”
// let obj = Object.create(); //Uncaught TypeError: Object prototype may only be an Object or null
// let obj = Object.create(Dog.prototype);
// let obj = Object.create(null); //->创建一个空对象,并且阻止了他的__proto__指向「没有这个属性了」
// console.log(obj);
复制代码

函数的多种角色

image.png

JQ源码分析

工厂模式

const jquery = require("./jquery");

function factory(window, noGlobal) {
    // window->window  noGlobal->undefined
    var arr = [];
    var slice = arr.slice; //Array.prototype.slice

    var version = "3.5.1",
        jQuery = function (selector, context) {
            return new jQuery.fn.init(selector, context);
        };
    jQuery.fn = jQuery.prototype = {
        constructor: jQuery,
        // 把JQ对象转换为原生对象
        get: function (num) {
            if (num == null) {
                // num=null/undefined
                return slice.call(this);
            }
            return num < 0 ? this[num + this.length] : this[num];
        },
        // 基于索引 最后返回的依然是实例对象
        eq: function (i) {
            var len = this.length,
                j = +i + (i < 0 ? len : 0);
            // this[j] 原生的,包在数组中
            return this.pushStack(j >= 0 && j < len ? [this[j]] : []);
        },
        pushStack: function (elems) {
            // this.constructor->jQuery  jQuery()空JQ实例
            // JQ对象:{0:xxx,length:1}
            var ret = jQuery.merge(this.constructor(), elems);
            ret.prevObject = this;
            return ret;
        },
        each: function (callback) {
            // $(...).each(callback)
            // this:JQ实例(类数组JQ对象)
            return jQuery.each(this, callback);
        },
    };

    jQuery.each = function each(obj, callback) {
        var length, i = 0;
        // isArrayLike:检测是否为数组或者类数组
        if (isArrayLike(obj)) {
            length = obj.length;
            for (; i < length; i++) {
                //每一轮循环都去执行回调函数
                //   + 传递实参:索引/当前项
                //   + 改变THIS:当前项
                //   + 接收返回值:如果回调函数返回false,则结束循环
                var result = callback.call(obj[i], i, obj[i]);
                if (result === false) {
                    break;
                }
            }
        } else {
            // 对象
            /* for (i in obj) {
                // for in遍历的问题:
                //    + 1.遍历到原型上自己扩展的公共的属性
                //    + 2.顺序 
                //    + 3.无法找到symbol的属性
                if (callback.call(obj[i], i, obj[i]) === false) {
                    break;
                }
            } */
            var keys = Object.getOwnPropertyNames(obj).concat(Object.getOwnPropertySymbols(obj));
            for (; i < keys.length; i++) {
                var key = keys[i];
                if (callback.call(obj[key], key, obj[key]) === false) {
                    break;
                }
            }
        }
        return obj;
    }

    var rootjQuery = jQuery(document);
    var rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/;
    var init = jQuery.fn.init = function (selector, context, root) {
        var match, elem;
        // HANDLE: $(""), $(null), $(undefined), $(false) 
        // 返回结果是一个JQ实例「空的实例对象」
        if (!selector) {
            return this;
        }
        // $('.xxx') => root=$(document)
        root = root || rootjQuery;
        // 选择器是一个字符串?
        if (typeof selector === "string") {
            if (selector[0] === "<" &&
                selector[selector.length - 1] === ">" &&
                selector.length >= 3) {
                match = [null, selector, null];
            } else {
                match = rquickExpr.exec(selector);
            }

            // Match html or make sure no context is specified for #id
            if (match && (match[1] || !context)) {

                // HANDLE: $(html) -> $(array)
                if (match[1]) {
                    context = context instanceof jQuery ? context[0] : context;

                    // Option to run scripts is true for back-compat
                    // Intentionally let the error be thrown if parseHTML is not present
                    jQuery.merge(this, jQuery.parseHTML(
                        match[1],
                        context && context.nodeType ? context.ownerDocument || context : document,
                        true
                    ));

                    // HANDLE: $(html, props)
                    if (rsingleTag.test(match[1]) && jQuery.isPlainObject(context)) {
                        for (match in context) {
                            // Properties of context are called as methods if possible
                            if (isFunction(this[match])) {
                                this[match](context[match]);
                                // ...and otherwise set as attributes
                            } else {
                                this.attr(match, context[match]);
                            }
                        }
                    }
                    return this;
                } else {
                    elem = document.getElementById(match[2]);

                    if (elem) {

                        // Inject the element directly into the jQuery object
                        this[0] = elem;
                        this.length = 1;
                    }
                    return this;
                }

                // HANDLE: $(expr, $(...))
            } else if (!context || context.jquery) {
                return (context || root).find(selector);

                // HANDLE: $(expr, context)
                // (which is just equivalent to: $(context).find(expr)
            } else {
                return this.constructor(context).find(selector);
            }

            // HANDLE: $(DOMElement)
        } else if (selector.nodeType) {
            // 选择器是一个节点「DOM元素节点/文本节点... JS获取的」
            this[0] = selector;
            this.length = 1;
            return this;
        } else if (isFunction(selector)) {
            // 选择器是一个函数  $(document).ready(函数) 「监听DOMContentLoaded事件:等到DOM结构加载完成,执行对应的方法」
            return root.ready !== undefined ?
                root.ready(selector) :
                selector(jQuery);
        }
        return jQuery.makeArray(selector, this);
    };
    init.prototype = jQuery.fn;

    // 浏览器环境下运行,条件成立的
    if (typeof noGlobal === "undefined") {
        window.jQuery = window.$ = jQuery;
    }
}
factory(window);


//===========
// $() -> 就是把jQuery方法执行的「普通函数」 “JQ选择器”  
// =>最后获取的结果是jQuery类的实例对象“JQ对象”
//    $('.box')
//    $('.box',conatiner)
/* $('.box')
jQuery('.box')
$.ajax({}); */
//...

//=> $(document).ready(函数)
/* $(function () {
    // 等待页面中的DOM结构渲染完,去执行回调函数
    // ...
}); */

// 基于JS方法获取的是原生DOM对象:可以调用内置的JS方法
// 基于$()获取的JQ对象,只能调JQ原型上的方法
// ===默认两种对象之间所用的方法不能混着调,想调用只能先相互转换
// 原生->JQ   $(原生对象)   {0:xxx,length:1...}  「类数组集合」
// JQ->原生   $xxx[索引] / $xxx.get(索引)
复制代码

image.png

JS四种数据类型检测

/*
 * JS中的数据类型检测都有哪些办法?
 *    + typeof [value]
 *      + 简单方便,大部分数据类型都已有效检测出来
 *      + typeof null ->"object"   JS设计的缺陷:数据值都是按照二进制存储的  1整数 010浮点数 100字符串 110布尔 000对象 -2^30undefined 000000null ...  =>也说明了typeof检测数据类型是按照二进制存储的值进行检测的
 *      + typeof不能细分具体的对象数据类型值,所有对象数据类型的值,检测出来的结果都是"object"
 *      + typeof检测基于构造函数创建出来的,基本数据类型的实例对象,结果也是"object"
 * 
 *    + Object.prototype.toString.call([value])
 *      + 万全之策
 *      + 大部分内置类的原型上都有toString,但是一般都是转换为字符串,只有Object.prototype上的toString并不是转换为字符串,而是返回当前实例对象所属类的信息的 “[object 所属构造函数的信息]”
 *        + 所属构造函数的信息是根据 Symbol.toStringTag 获取的「有这个属性基于这个获取,没有浏览器自己计算」
 *      let obj={name:'zhufeng'};
 *        obj.toString -> Object.prototype.toString
 *      let arr=[];
 *        arr.toString -> Array.prototype.toString
 *      鸭子类型「原型上方法的借用」
 *      =>Object.prototype.toString.call(arr)
 *      =>({}).toString.call(arr)
 *  
 *    + instanceof 
 *      + 检测某个实例是否属于这个类的
 *      + 基于instanceof可以细分一下不同类型的对象「也可以检测出基于构造函数方式创建出来的基本类型对象值」   
 *      + 临时当“壮丁”的,存在很多问题
 *        + 原理:构造函数[Symbol.hasInstance](实例)
 *        + 原理:检测当前构造函数的原型prototype是否出现在,当前实例所处的原型链上__proto__,如果能出现结果就是true
 *        + 在JS中原型链是可以改动的,所有结果不准确
 *        + 所有实例的原型链最后都指向Object.prototype,所以 “实例 instacnceof Object”的结果都是true
 *        + 字面量方式创造的基本数据类型值是无法基于 instanceof 检测的「浏览器默认并不会把它转换为new的方式」,所以它本身不是对象,不存在__proto__这个东西
 *        + ...
 * 
 *    + constructor
 *      + 临时当“壮丁”的,也存在很多问题
 *      + constructor是可以肆意被修改,所以也不准
 */
 
 
/* typeof
// JS中创建一个值有两种方案:
//   1.字面量方式
let n = 100;
let obj1 = {};

//   2.构造函数方式 「不能 new Symbol/new BigInt -> Object(symbol/bigint) 其他基本类型值也可以这样处理,但是都要排除null/undefined」
let m = new Number(100);
let obj2 = new Object();

// 对于基本数据类型,两种方式的结果是不一样的:
// 字面量方式得到的是基本数据类型「特殊的实例」,而构造函数方式得到的是对象类型「正规的实例」
// 对于引用数据类型,两种方式除了语法上的一些区别,没有本质的区别,获取的都是对应类的实例对象 
*/


/* 
let arr = [10, 20];
let obj = {
    0: 10,
    1: 20,
    length: 2
};
let m = new Number(100);
let n = 100;
console.log(arr instanceof Array); //->true
console.log(arr instanceof Object); //->true
console.log(obj instanceof Array); //->false
console.log(m instanceof Number); //->true 
console.log(n instanceof Number); //->false 
*/
/* 
class Fn {
    static[Symbol.hasInstance]() {
        console.log('OK');
        return false;
    }
}
let f = new Fn;
console.log(f instanceof Fn);
*/
/* 
function Fn() {}
Fn.prototype = Array.prototype;
let f = new Fn;
console.log(f instanceof Array); 
*/


/* 
let arr = [10, 20];
let obj = {
    0: 10,
    1: 20,
    length: 2
};
let n = 100;
let m = new Number(100);
console.log(arr.constructor === Array); //->true
console.log(obj.constructor === Array); //->false
console.log(arr.constructor === Object); //->false
console.log(n.constructor === Number); //->true
console.log(m.constructor === Number); //->true 
*/


/* let class2type = {};
let toString = class2type.toString; //=>Object.prototype.toString
console.log(toString.call(1));
console.log(toString.call(new Number(1)));
console.log(toString.call('zhufeng'));
console.log(toString.call(true));
console.log(toString.call(null));
console.log(toString.call(undefined));
console.log(toString.call([10, 20]));
console.log(toString.call(/^\d+$/));
console.log(toString.call({}));
console.log(toString.call(function () {})); */

/* function* fn() {}
console.log(Object.prototype.toString.call(fn)); //->"[object GeneratorFunction]" */

/* 
function Fn() {}
Fn.prototype[Symbol.toStringTag] = 'Fn';
let f = new Fn;
console.log(Object.prototype.toString.call(f)); //->“[object Fn]” 
*/

/* 
let arr = [];
console.log(Array.isArray(arr)); //->true
console.log(Object.prototype.toString.call(arr) === "[object Array]"); //->true
console.log(/array/i.test(Object.prototype.toString.call(arr))); //->true 
*/
复制代码

JQ数据类型检测方法封装

(function () {
    var class2type = {};
    var toString = class2type.toString; //Object.prototype.toString 检测数据类型的
    var hasOwn = class2type.hasOwnProperty; //Object.prototype.hasOwnProperty 检测是否私有属性的
    var fnToString = hasOwn.toString; //Function.prototype.toString 把函数转换为字符串
    var ObjectFunctionString = fnToString.call(Object); //=>"function Object() { [native code] }"
    var getProto = Object.getPrototypeOf; //获取当前对象的原型链__proto__

    // 建立数据类型检测的映射表 { "[object Array]":"array",....}
    var mapType = ["Boolean", "Number", "String", "Function", "Array", "Date", "RegExp", "Object", "Error", "Symbol", "BigInt"];
    mapType.forEach(function (name) {
        class2type["[object " + name + "]"] = name.toLocaleLowerCase();
    });

    // 检测数据类型的办法
    var toType = function toType(obj) {
        if (obj == null) {
            // 传递的是 null/undefined
            return obj + "";
        }
        // 基于字面量方式创造的基本数据类型,直接基于typeof检测即可「性能要高一些」;
        // 剩余的基于Object.prototype.toString.call的方式来检测,把获取的值到映射表中匹配,匹配结果是字符串对应的数据类型;
        return typeof obj === "object" || typeof obj === "function" ?
            class2type[toString.call(obj)] || "object" :
            typeof obj;
    };

    // 检测是否为函数
    var isFunction = function isFunction(obj) {
        // typeof obj.nodeType !== "number" :防止在部分浏览器中,检测<object>元素对象结果也是"function",但是它的nodeType=1,处理浏览器兼容问题
        return typeof obj === "function" && typeof obj.nodeType !== "number";
    };

    // 检测是否为window对象
    var isWindow = function isWindow(obj) {
        // window.window===window 符合这个条件的就是window对象
        return obj != null && obj === obj.window;
    };

    // 检测是否为数组或者类数组
    var isArrayLike = function isArrayLike(obj) {
        // length存储的是对象的length属性值或者是false
        // type存储的是检测的数据类型
        var length = !!obj && "length" in obj && obj.length,
            type = toType(obj);

        // window.length=0 && Function.prototype.length=0
        if (isFunction(obj) || isWindow(obj)) return false;

        // type === "array" 数组
        // length === 0 空的类数组
        // 最后一个条件判断的是非空的类数组「有length属性,并且最大索引在对象中」
        return type === "array" || length === 0 ||
            typeof length === "number" && length > 0 && (length - 1) in obj;
    };

    // 检测是否为纯粹的对象  例如:{}
    var isPlainObject = function isPlainObject(obj) {
        var proto, Ctor;

        // 不存在或者基于toString检测结果都不是[object Object],那么一定不是纯粹的对象
        if (!obj || toString.call(obj) !== "[object Object]") {
            return false;
        }

        // 获取当前值的原型链「直属类的原型链」
        proto = getProto(obj);

        // Object.create(null):这样创造的对象没有__proto__
        if (!proto) return true;

        // Ctor存储原型对象上的constructor属性,没有这个属性就是false
        Ctor = hasOwn.call(proto, "constructor") && proto.constructor;

        // 条件成立说明原型上的构造函数是Object:obj就是Object的一个实例,并且obj.__proto__===Object.prototype
        return typeof Ctor === "function" && fnToString.call(Ctor) === ObjectFunctionString;
    };

    // 检测是否为空对象
    var isEmptyObject = function isEmptyObject(obj) {
        // 排除非对象
        if (obj == null) return false;
        if (typeof obj !== "object") return false;

        // 是一个对象「纯粹对象或者特殊对象都可以」
        var keys = Object.keys(obj);
        if (hasOwn.call(Object, 'getOwnPropertySymbols')) {
            // 兼容这个属性的情况下,我们再去拼接
            keys = keys.concat(Object.getOwnPropertySymbols(obj));
        }
        return keys.length === 0;
    };

    // 检测是否为数字
    var isNumeric = function isNumeric(obj) {
        var type = toType(obj);
        return (type === "number" || type === "string") && !isNaN(+obj);
    };

    // 暴露到外部
    var utils = {
        toType: toType,
        isFunction: isFunction,
        isWindow: isWindow,
        isArrayLike: isArrayLike,
        isPlainObject: isPlainObject,
        isEmptyObject: isEmptyObject,
        isNumeric: isNumeric
    };
    if (typeof window !== "undefined") {
        window._ = window.utils = utils;
    }
    if (typeof module === "object" && typeof module.exports === "object") {
        module.exports = utils;
    }
})();
复制代码

JS多种继承方式

/*
 * JS本身是基于面向对象开发的编程语言
 * =>类:封装、继承、多态
 *
 * 封装:类也是一个函数,把实现一个功能的代码进行封装,以此实现“低耦合高内聚”
 * 多态:重载、重写
 *    重写:子类重写父类上的方法(伴随继承运行的)
 *    重载:相同的方法,由于参数或者返回值不同,具备了不同的功能(JS中不具备严格意义上的重载, JS中的重载:同一个方法内,根据传参不同实现不同的功能)
 *    继承:子类继承父类中的方法
 */
 
/*
public void fn(int x, int y){

}
public void fn(int x){

}
fn(10, 20); 执行第一个fn
fn(10); 执行第二个fn
fn('string') 报错
*/
 
/*
function fn(x, y){

}
function fn(x){

}
fn(10, 20); 执行第一个fn
fn(10); 执行第二个fn
*/
 
/*
function fn(x, y){
 if(y === undefined){
     // ...
     return
 }
 // ...
}

fn(10, 20);
fn(10);
*/
复制代码
/*
 * 在JS语言中,它的继承和其他编程语言还是不太一样的
 * 继承的目的:让子类的实例同时也具备父类中私有的属性和公共的方法
 */
function Parent() {
    this.x = 100
}
Parent.prototype.getX = function getX () {
    return this.x
}
function Child () {
    this.y = 200
}
Child.prototype.getY = function getY () {
    return this.y
}
let c1 = new Child
console.log(c1)

// JS中第一种继承方案:原型继承(让子类的原型等于父类的实例即可)
function Parent() {
    this.x = 100
}
Parent.prototype.getX = function getX () {
    return this.x
}
function Child () {
    this.y = 200
}
Child.prototype = new Parent() // =>原型继承
Child.prototype.getY = function getY () {
    return this.y
}
let c1 = new Child
console.log(c1)
复制代码

image.png

// JS中第二种继承方案:call继承(只能继承父类中私有的,不能继承父类中公共的)
function Parent() {
    this.x = 100
}
Parent.prototype.getX = function getX () {
    return this.x
}
function Child () {
    // 在子类构造函数中,把父类当做普通函数执行(没有父类实例,父类原型上的那些东西也就和它没关系了)
    // this -> Child的实例c1
    Parent.call(this) // this.x = 100 相当于强制给c1这个实例设置一个私有的属性x,相当于让子类的实例继承了父类的私有属性,并且也变为了子类私有的属性“拷贝式”
    this.y = 200
}
Child.prototype.getY = function getY () {
    return this.y
}
let c1 = new Child
console.log(c1)
复制代码
// JS中第三种继承方案:寄生组合继承(call继承 + 另类原型继承)
function Parent() {
    this.x = 100
}
Parent.prototype.getX = function getX () {
    return this.x
}
function Child () {
    Parent.call(this)
    this.y = 200
}
// Child.prototype = deepClone(Parent.prototype)
// Child.prototype.__proto__ = Parent.prototype
Child.prototype = Object.create(Parent.prototype)
Child.prototype.constructor = Child
Child.prototype.getY = function getY () {
    return this.y
}
let c1 = new Child
console.log(c1)
复制代码

image.png

image.png

// ES6中类和继承
class Parent {
    constructor () {
        this.x = 100
    }
    // Parent.prototype.getX = function () {}
    getX() {
        return this.x
    }
}
// 继承:extends Parent(类似寄生组合继承)
// 注意:继承后一定要在constructor第一行加上super()
class Child extends Parent {
    constructor () {
        super() //=>类似call继承 super(100,200):相当于把Parent中的constructor执行,传递了100和200
        this.y = 200
    }
    getY() {
        return this.y
    }
}
// es6中创建的类,不能当作普通函数执行,只能new执行
复制代码

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享