珠峰web高级7期(二)

746 阅读15分钟

面向对象

构造函数 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执行

JQ中的extend和对象的深浅合并

// extend:给JQ的原型和对象扩展方法的
//   + $.extend({ xxx:function... })  向JQ对象上扩展方法「工具类的方法 -> 完善类库」
//   + $.fn.extend({ xxx:function... }) 向JQ原型上扩展方法「供实例调用 -> JQ插件」
/* $.extend({
    AAA: function () {
        // this->jQuery
    }
});
$.AAA();

$.fn.extend({
    BBB: function () {
        // this->jQuery实例对象
    }
});
$('body').BBB(); */

/* jQuery.extend = jQuery.fn.extend = function (obj) {
    if (obj == null || typeof obj !== "object") throw new TypeError('obj must be an object!');
    var self = this,
        keys = Object.keys(obj);
    typeof Symbol !== "undefined" ? keys = keys.concat(Object.getOwnPropertySymbols(obj)) : null;
    keys.forEach(function (key) {
        self[key] = obj[key];
    });
    return self;
}; */

// JQ中的extend还有一个功能:基于浅比较和深比较,实现对象的合并
//   + $.extend(obj1,obj2)  浅合并:obj2替换obj1,最后返回的是obj1   类似于:Object.assign
//   + $.extend(true,obj1,obj2) 深合并:obj2替换obj1,最后返回的是obj1
jQuery.extend = jQuery.fn.extend = function () {
    var options, name, src, copy, copyIsArray, clone,
        target = arguments[0] || {},
        i = 1,
        length = arguments.length,
        deep = false;

    // Handle a deep copy situation
    if (typeof target === "boolean") {
        deep = target;

        // Skip the boolean and the target
        target = arguments[i] || {};
        i++;
    }

    // Handle case when target is a string or something (possible in deep copy)
    if (typeof target !== "object" && !isFunction(target)) {
        target = {};
    }

    // Extend jQuery itself if only one argument is passed
    if (i === length) {
        target = this;
        i--;
    }

    for (; i < length; i++) {

        // Only deal with non-null/undefined values
        if ((options = arguments[i]) != null) {

            // Extend the base object
            for (name in options) {
                copy = options[name];

                // Prevent Object.prototype pollution
                // Prevent never-ending loop
                if (name === "__proto__" || target === copy) {
                    continue;
                }

                // Recurse if we're merging plain objects or arrays
                if (deep && copy && (jQuery.isPlainObject(copy) ||
                        (copyIsArray = Array.isArray(copy)))) {
                    src = target[name];

                    // Ensure proper type for the source value
                    if (copyIsArray && !Array.isArray(src)) {
                        clone = [];
                    } else if (!copyIsArray && !jQuery.isPlainObject(src)) {
                        clone = {};
                    } else {
                        clone = src;
                    }
                    copyIsArray = false;

                    // Never move original objects, clone them
                    target[name] = jQuery.extend(deep, clone, copy);

                    // Don't bring in undefined values
                } else if (copy !== undefined) {
                    target[name] = copy;
                }
            }
        }
    }

    // Return the modified object
    return target;
};
let obj1 = {
    name: '在线Web高级',
    teacher: {
        0: 'zhouxiaotian',
        1: 'renjinhui'
    },
    price: '不高'
};
// obj1.A = obj1

let obj2 = {
    name: 'CSS高级进阶',
    teacher: {
        2: 'limeng'
    },
    to: 'CSS基础薄弱人群'
};
// obj2.A = obj2

// let obj = Object.assign(obj1, obj2); //浅合并

// $.extend(obj1, obj2); //浅合并
// $.extend(true, obj1, obj2); //深合并
// console.log(obj1);
(function () {
    var class2type = {};
    var toString = class2type.toString;
    var hasOwn = class2type.hasOwnProperty;
    var fnToString = hasOwn.toString;
    var ObjectFunctionString = fnToString.call(Object);
    var getProto = Object.getPrototypeOf;

    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) {
            return obj + "";
        }
        return typeof obj === "object" || typeof obj === "function" ?
            class2type[toString.call(obj)] || "object" :
            typeof obj;
    };

    var isFunction = function isFunction(obj) {
        return typeof obj === "function" && typeof obj.nodeType !== "number";
    };

    var isWindow = function isWindow(obj) {
        return obj != null && obj === obj.window;
    };

    var isArrayLike = function isArrayLike(obj) {
        var length = !!obj && "length" in obj && obj.length,
            type = toType(obj);
        if (isFunction(obj) || isWindow(obj)) return false;
        return type === "array" || length === 0 ||
            typeof length === "number" && length > 0 && (length - 1) in obj;
    };

    var isPlainObject = function isPlainObject(obj) {
        var proto, Ctor;
        if (!obj || toString.call(obj) !== "[object Object]") {
            return false;
        }
        proto = getProto(obj);
        if (!proto) return true;
        Ctor = hasOwn.call(proto, "constructor") && proto.constructor;
        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 each = function each(obj, callback) {
        var length, i = 0;
        if (isArrayLike(obj)) {
            length = obj.length;
            for (; i < length; i++) {
                var result = callback.call(obj[i], i, obj[i]);
                if (result === false) {
                    break;
                }
            }
        } else {
            var keys = Object.keys(obj);
            typeof Symbol !== "undefined" ? keys = keys.concat(Object.getOwnPropertySymbols(obj)) : null;
            for (; i < keys.length; i++) {
                var key = keys[i];
                if (callback.call(obj[key], key, obj[key]) === false) {
                    break;
                }
            }
        }
        return obj;
    }

    /* 
     * 对象的深浅合并
     *  [合并的规律]
     *    A->obj1  B->obj2
     *    A/B都是对象:迭代B,依次替换A
     *    A不是对象,B是对象:B替换A
     *    A是对象,B不是对象:依然以A的值为主
     *    A/B都不是对象:B替换A
     */
    var shallowMerge = function shallowMerge(obj1, obj2) {
        var isPlain1 = isPlainObject(obj1),
            isPlain2 = isPlainObject(obj2);
        if (!isPlain1) return obj2;
        if (!isPlain2) return obj1;
        each(obj2, function (key, value) {
            obj1[key] = value;
        });
        return obj1;
    };
    var deepMerge = function deepMerge(obj1, obj2, cache) {
        // 防止对象的循环嵌套导致的死递归问题
        cache = !Array.isArray(cache) ? [] : cache;
        if (cache.indexOf(obj2) >= 0) return obj2;
        cache.push(obj2);

        // 正常处理
        var isPlain1 = isPlainObject(obj1),
            isPlain2 = isPlainObject(obj2);
        if (!isPlain1 || !isPlain2) return shallowMerge(obj1, obj2);
        each(obj2, function (key, value) {
            obj1[key] = deepMerge(obj1[key], value, cache);
        });
        return obj1;
    };

    /*
     * 对象或者数组的深浅克隆 
     */
    var shallowClone = function shallowClone(obj) {
        var type = toType(obj),
            Ctor = null;
        // 其他特殊值的处理
        if (obj == null) return obj;
        Ctor = obj.constructor;
        if (/^(regexp|date)$/i.test(type)) return new Ctor(obj);
        if (/^(symbol|bigint)$/i.test(type)) return Object(obj);
        if (/^error$/i.test(type)) return new Ctor(obj.message);
        if (/^function$/i.test(type)) {
            return function anonymous() {
                return obj.apply(this, arguments);
            };
        }
        // 数组和纯粹对象,我们基于循环的方案来处理
        if (isPlainObject(obj) || type === "array") {
            var result = new Ctor();
            each(obj, function (key, value) {
                result[key] = value;
            });
            return result;
        }
        return obj;
    };
    var deepClone = function deepClone(obj, cache) {
        var type = toType(obj),
            Ctor = null,
            result = null;
        if (!isPlainObject(obj) && type !== "array") return shallowClone(obj);
        // 防止死递归
        cache = !Array.isArray(cache) ? [] : cache;
        if (cache.indexOf(obj) >= 0) return obj;
        cache.push(obj);
        // 正常的迭代处理
        Ctor = obj.constructor;
        result = new Ctor();
        each(obj, function (key, value) {
            result[key] = deepClone(value, cache);
        });
        return result;
    };
    

    // 暴露到外部
    var utils = {
        toType: toType,
        isFunction: isFunction,
        isWindow: isWindow,
        isArrayLike: isArrayLike,
        isPlainObject: isPlainObject,
        isEmptyObject: isEmptyObject,
        isNumeric: isNumeric,
        each: each,
        shallowMerge: shallowMerge,
        deepMerge: deepMerge,
        shallowClone: shallowClone,
        deepClone: deepClone
    };
    if (typeof window !== "undefined") {
        window._ = window.utils = utils;
    }
    if (typeof module === "object" && typeof module.exports === "object") {
        module.exports = utils;
    }
})();

对象和数组的深浅克隆

let obj = {
    a: 100,
    b: [10, 20, 30],
    c: {
        x: 10
    },
    d: /^\d+$/,
    fn: function () {},
    time: new Date,
    xx: Symbol(),
    0: null,
    1: undefined
};
obj.A = obj;

let arr = [10, [100, 200], {
    x: 10,
    y: 20
}];

let obj2 = _.deepClone(obj);
let arr2 = _.deepClone(arr);


// 深克隆  JSON.parse/stringify「变为字符串,再变为对象,这样所有的内存会重新开辟一下」
//   + 转换为字符串的时候,不是所有的值都支持
//   + 正则变为空对象
//   + BigInt处理不了,会报错
//   + 属性值为undefined或者函数的都会消失
//   + 日期对象变为字符串后转换不回来了
//   + ArrayBuffer...
/* let obj2 = JSON.parse(JSON.stringify(obj));
console.log(obj2 === obj); //->false
console.log(obj2.b === obj.b); //->false */


// 对象/数组克隆 「浅克隆」
//  + ...
//  + 迭代
//  + 内置方法 例如:slice
/* 
let obj2 = {};
_.each(obj, (key, value) => {
    obj2[key] = value;
});

let obj2 = {
    ...obj
};

let arr2 = arr.slice(); 
*/

重写instanceof

// instanceof:检测原理
//   + 构造函数 Symbol.hasInstance 属性方法
//   + 检测构造函数的prototype是否出现在实例的__proto__上
//   + 不能检测基本数据类型,检测的实例必须都是对象「自己的方法想把基本数据类型也处理了」
//   + ...

function instance_of(example, classFunc) {
    // 参数初始化
    if (typeof classFunc !== "function") throw new TypeError("Right-hand side of 'instanceof' is not callable");
    if (example == null) return false;

    // 支持Symbol的并且拥有Symbol.hasInstance,以这个处理
    if (typeof Symbol !== "undefined") {
        var hasInstance = classFunc[Symbol.hasInstance];
        if (typeof hasInstance === "function") {
            return hasInstance.call(classFunc, example);
        }
    }
    
    // 不支持的则基于检测原型链来实现
    var prototype = classFunc.prototype,
        proto = Object.getPrototypeOf(example);
    if (!prototype) return false; // 没有prototype的函数(例如:箭头函数)直接返回false
    while (true) {
        // 找到Object.prototype.__proto__
        if (proto === null) return false;
        // 在原型链上找到了类的原型
        if (proto === prototype) return true;
        proto = Object.getPrototypeOf(proto);
    }
}
let res = instance_of([12, 23], Array);
console.log(res); //=>true  

res = instance_of([12, 23], Object);
console.log(res); //=>true  

res = instance_of([12, 23], RegExp);
console.log(res); //=>false  

面向对象习题

/*
 * ===绝对相等:左右两边类型和值都一致才相等
 * ==相等:左右两边类型不同,会默认先转换为相同的类型,再去比较
 *    对象==字符串:对象转字符串    
 *    null==undefined:相等,但是和其它值都不等
 *    NaN==NaN:false NaN和谁都不相等
 *    剩余的都是转换为数字
 * 对象->数字/字符串
 *    + 先调取这个属性 Symbol.toPrimitive
 *    + 没有这个属性,再去调用 valueOf 获取原始值「基本类型值」
 *    + 没有原始值,再去调用 toString 变为字符串
 *    + 如果最后是转换为数字,再去调用Number,把字符串转换为数字
 *    + ...
 */
/* let obj = {};
obj[Symbol.toPrimitive] = function toPrimitive(hint) {
    console.log(hint); //'number' / 'string' / 'default'
    return 0;
}; */


// 方案1:数据类型转换
var a = {
    i: 0
};
// Symbol.toPrimitive/valueOf/toString...
a[Symbol.toPrimitive] = function () {
    // this -> a
    return ++this.i;
};
if (a == 1 && a == 2 && a == 3) {
    console.log('OK');
}

var a = [1, 2, 3];
a.toString = a.shift;
if (a == 1 && a == 2 && a == 3) {
    console.log('OK');
}


// 方案2:数据劫持
//   + 在全局上下文中基于var/function声明变量,相当于给window设置对应的属性  -> window.a
//   + Object.defineProperty劫持对象中某个属性的获取和设置等操作
var i = 0;
Object.defineProperty(window, 'a', {
    get() {
        // 获取window.a的时候触发getter函数
        return ++i;
    },
    // set(value) {
    //     // 设置window.a属性值的时候触发setter函数
    // }
});
if (a == 1 && a == 2 && a == 3) {
    console.log('OK');
}

/* Array.prototype.push = function push(value) {
    // this -> arr
    // value -> 40
    // 操作1:把value放置在了this的末尾 this[this.length]=value
    // 操作2:把数组的length累加
    // 返回结果是新增后数组的长度
};
let arr = [10, 20, 30];
arr.push(40); */

let obj = {
    2: 3,
    3: 4,
    length: 2,
    push: Array.prototype.push
};
obj.push(1); //this->obj  value->1    obj[2]=1  obj.length=3
obj.push(2); //this->obj  value->2    obj[3]=2  obj.length=4
console.log(obj); //=>{2:1,3:2,length:4,push:...}

// [].push.call(obj, 1); // obj.push(1)
// ES6基于class创建的类:只能new执行,无法当做普通函数执行「Class constructor Modal cannot be invoked without 'new'」
class Modal {
    //-----给实例设置私有的属性  实例.x
    // 构造函数体
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }
    // z = 100; //相当于在构造函数体中 this.z=100

    //-----给构造函数原型上设置属性方法「实例的公共属性」  实例.getX()
    // + 设置方法如下即可
    // + 设置属性不可以
    getX() {
        console.log(this.x);
    }
    getY() {
        console.log(this.y);
    }

    //-----给构造函数设置静态属性方法「把它当做普通对象」 Modal.setNumber()
    static n = 200;
    static setNumber(n) {
        this.n = n;
    }
}
Modal.prototype.z = 100; */

/* function Modal(x, y) {
    this.x = x;
    this.y = y;
}
Modal.prototype.z = 10;
Modal.prototype.getX = function () {
    console.log(this.x);
}
Modal.prototype.getY = function () {
    console.log(this.y);
}
Modal.n = 200;
Modal.setNumber = function (n) {
    this.n = n;
};
let m = new Model(10, 20);
/*
 * 编写queryURLParams方法实现如下的效果(至少两种方案)
 *   + 字符串拆分「考虑到是否存在问号和井号」 -> 获取“?”/“#”后面的信息
 *   + 动态创建A标签,基于内置属性获取 
 */
String.prototype.queryURLParams = function queryURLParams(key) {
    // this->url  key->property
    // 获取信息
    var self = this,
        link = document.createElement('a'),
        hash = '',
        search = '',
        result = {};
    link.href = self;
    hash = link.hash;
    search = link.search;

    // 解析结果
    if (hash) {
        hash = hash.substring(1);
        result['_HASH'] = hash;
    }
    if (search) {
        search = search.substring(1);
        search.split('&').forEach(function (item) {
            item = item.split('=');
            result[item[0]] = item[1];
        });
    }

    // 返回信息
    return typeof key === "undefined" ? result : result[key];
};

String.prototype.queryURLParams = function queryURLParams(key) {
    var self = this,
        result = {};
    self.replace(/#([^?#=&]+)/g, function (_, $1) {
        result['_HASH'] = $1;
    });
    self.replace(/([^?#=&]+)=([^?#=&]+)/g, function (_, $1, $2) {
        result[$1] = $2;
    });
    return typeof key === "undefined" ? result : result[key];
};

let url = "http://www.zhufengpeixun.cn/?lx=1&from=wx#video";
// ->{lx:1,from:'wx',_HASH:'video'}
console.log(url.queryURLParams("from")); //=>"wx"
console.log(url.queryURLParams("_HASH")); //=>"video"
console.log(url.queryURLParams()); 
var validate = function validate(x) {
    x = +x;
    return isNaN(x) ? 0 : x;
};
Number.prototype.plus = function plus(x) {
    x = validate(x);
    // this都是对象数据类型的值  this->10/new Number(10)
    return this + x;
};
Number.prototype.minus = function minus(x) {
    x = validate(x);
    return this - x;
};
let n = 10;
let m = n.plus(10).minus(5);
console.log(m); //=>15(10+10-5)
function Foo() {
    getName = function () {
        console.log(1);
    };
    return this;
}
Foo.getName = function () {
    console.log(2);
};
Foo.prototype.getName = function () {
    console.log(3);
};
var getName = function () {
    console.log(4);
};

function getName() {
    console.log(5);
}
Foo.getName();
getName();
Foo().getName();
getName();
new Foo.getName();
new Foo().getName();
new new Foo().getName();

image.png

function Fn() {
    let a = 1;
    this.a = a;
}
Fn.prototype.say = function () {
    this.a = 2;
}
Fn.prototype = new Fn;
let f1 = new Fn;
Fn.prototype.b = function () {
    this.a = 3;
};
console.log(f1.a);
console.log(f1.prototype);
console.log(f1.b);
console.log(f1.hasOwnProperty('b'));
console.log('b' in f1);
console.log(f1.constructor == Fn);

image.png

function C1(name) {
    if (name) {
        this.name = name;
    }
}
function C2(name) {
    this.name = name;
}

function C3(name) {
    this.name = name || 'join';
}
C1.prototype.name = 'Tom';
C2.prototype.name = 'Tom';
C3.prototype.name = 'Tom';
alert((new C1().name) + (new C2().name) + (new C3().name));
// 'Tom' + undefined + 'join' => 'Tomundefinedjoin'

image.png