珠峰web高階7期(二)

語言: CN / TW / HK

面向物件

建構函式 VS 普通函式

js /* * 程式語言 * 面向物件 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

物件遍歷問題及解決方案

```js 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

```js 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

```js / * 向內建類的原型擴充套件方法 * + 內建類的原型上提供了很多內建方法,但是這些方法不一定完全滿足業務需求,此時需要我們自己擴充套件一些方法 * 「優勢」 * + 呼叫起來方便 * + 可以實現鏈式寫法 * + 限定調取方法的型別,必須是指定類的例項 * + 擴充套件的方法,各個模組「其他成員」都可以直接的呼叫 * + ... * 「弊端」 * + 自己擴充套件的方法,容易覆蓋內建的方法 (解決:自己設定的方法名要設定字首 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

```js / 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原始碼分析

工廠模式

```js 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 / * 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 { staticSymbol.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資料型別檢測方法封裝

```js (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中不具備嚴格意義上的過載, 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 / * 在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 // 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 // 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 js // 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和物件的深淺合併

```js // 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;

}; js 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); js (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;
}

})(); ```

物件和陣列的深淺克隆

```js 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

```js // 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
```

面向物件習題

```js / * ===絕對相等:左右兩邊型別和值都一致才相等 * ==相等:左右兩邊型別不同,會預設先轉換為相同的型別,再去比較 * 物件==字串:物件轉字串
* 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'); } js

/ 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) js // 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); js / * 編寫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()); js 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) js 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 js 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 ```js 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