Javascript基礎(chǔ)進(jìn)階:面向?qū)ο蠛驮驮玩?/h1>
* 學(xué)習(xí)進(jìn)階方式 💪
基礎(chǔ)知識(shí)要夯實(shí);
原理源碼要深入;
深度廣度要擴(kuò)展;
簡(jiǎn)短的概括:
1、面向?qū)ο罄碚撝R(shí)總述
2、自定義類的創(chuàng)建和一些細(xì)節(jié)知識(shí)
3、面向?qū)ο笾械脑秃驮玩?/p>
4、重寫內(nèi)置new以及基于內(nèi)置類原型擴(kuò)展方法
5、THIS情況匯總及CALL、APPLY、BIND的應(yīng)用
面向?qū)ο罄碚撝R(shí)總述
* 核心答案 | 基礎(chǔ)知識(shí)要夯實(shí)
編程語(yǔ)言
1、OOP面向?qū)ο螅?/strong>
1)java,
2)python,
3)C++,
4)php,
5)C#(ASP.NET),
6)javascript -> Node.js
2、POP面向過(guò)程:
HTML和CSS是標(biāo)記語(yǔ)言
1)less/sass/stylus:CSS預(yù)編譯語(yǔ)言,讓CSS具備面向?qū)ο缶幊痰奶攸c(diǎn)。
2)寫完的代碼無(wú)法被瀏覽器直接識(shí)別,需要編譯后(編譯成為正常的CSS)在瀏覽器中渲染。
什么是面向?qū)ο缶幊蹋?/span>
1、對(duì)象:泛指,萬(wàn)物皆對(duì)象(JS中所有我們學(xué)習(xí)研究和開(kāi)發(fā)的都是對(duì)象 「研究對(duì)象」);
2、類:對(duì) “對(duì)象” 的一個(gè)細(xì)分,按照對(duì)應(yīng)的功能特點(diǎn),分成我們的大類和小類「類別」;
3、實(shí)例:某個(gè)類別中具體的事物;
* 關(guān)于類的“封裝、繼承、多態(tài)”
1)封裝:把實(shí)現(xiàn)某個(gè)功能的代碼封裝到函數(shù)中,起到“低耦合高內(nèi)聚”的作用
2)繼承:子類及子類的實(shí)例繼承了父類中的屬性和方法
3)多態(tài):函數(shù)的重載(方法名字相同,但是傳遞參數(shù)的個(gè)數(shù)或者類型不同,識(shí)別為兩個(gè)不同的方法 -> 后臺(tái)語(yǔ)言有這個(gè)特征,但是JS中不存在嚴(yán)格意義上的重載)和重寫(子類重寫父類的方法)
JS就是基于“面向?qū)ο?rdquo;思想設(shè)計(jì)的編程語(yǔ)言
1、本身存在很多“內(nèi)置類”
1)每一個(gè)數(shù)據(jù)類型都有一個(gè)自己所屬的內(nèi)置類
2)獲取的元素集合或者節(jié)點(diǎn)集合也是有自己的類 HTMLCollection / NodeList
3)每一個(gè)元素標(biāo)簽都有自己所屬的類
2、我們學(xué)習(xí)JS:拿出某個(gè)類的一個(gè)實(shí)例去研究和學(xué)習(xí),當(dāng)前實(shí)例研究明白后,那么當(dāng)前實(shí)例所屬類下的其他實(shí)例,也具備這些特點(diǎn)...
自定義類的創(chuàng)建和一些細(xì)節(jié)知識(shí)
* 核心答案 | 基礎(chǔ)知識(shí)要夯實(shí)
自定義類(所有的類「內(nèi)置類/自定義類」都是“函數(shù)數(shù)據(jù)類型”的值)
函數(shù)執(zhí)行的時(shí)候基于new執(zhí)行即可 “構(gòu)造函數(shù)執(zhí)行”。
🌰 例如一:普通函數(shù) 與 構(gòu)造函數(shù)
- function Fn(x, y) {
- let total = x + y;
- this.x = x;
- this.y = y;
- return total;
- }
- // 1、作為普通函數(shù)執(zhí)行
- Fn(10, 20);
- // 2、構(gòu)造函數(shù)執(zhí)行
- // 說(shuō)明:f1是Fn這個(gè)類的一個(gè)實(shí)例對(duì)象
- let f1 = new Fn(10, 20);
- console.log(f1.x, f1.y, f1.total);
- // 說(shuō)明:total只是上下文中的私有變量,和實(shí)例f1沒(méi)有關(guān)系」
- // 結(jié)果:10 20 undefined
畫圖分析:( 有圖有真相 )
構(gòu)造函數(shù) VS 普通函數(shù)
1、構(gòu)造函數(shù)執(zhí)行,最開(kāi)始會(huì)像普通函數(shù)執(zhí)行一樣,形成私有的上下文。
1)AO;
2) SCOPE-CHAIN;
3)形參賦值;
4)變量提升;
5)代碼執(zhí)行;
不同的地方:
1、創(chuàng)建上下文之后,瀏覽器默認(rèn)幫助我們創(chuàng)建一個(gè)對(duì)象 “實(shí)例對(duì)象”。
1)把當(dāng)前Fn函數(shù)當(dāng)作一個(gè)類“構(gòu)造函數(shù)”
2)創(chuàng)建的對(duì)象就是這個(gè)類的一個(gè)實(shí)例
2、初始this的時(shí)候,讓this指向當(dāng)前創(chuàng)建的實(shí)例對(duì)象。
3、在代碼執(zhí)行完,返回值的時(shí)候
1)如果函數(shù)沒(méi)有寫return,或者返回的是一個(gè)基本數(shù)據(jù)類型值,則瀏覽器默認(rèn),會(huì)把創(chuàng)建的實(shí)例對(duì)象返回;
2)如果函數(shù)本身返回的就是一個(gè)引用數(shù)據(jù)類型值,還是以自己返回的為主。
🌰 例如二:構(gòu)造函數(shù)擴(kuò)展
- function Fn(x, y) {
- let total = x + y;
- this.x = x;
- this.y = y;
- return {
- name: '前端學(xué)苑'
- };
- }
說(shuō)明:
由于構(gòu)造函數(shù)體中,默認(rèn)自己返回一個(gè)引用類型值,所以f1不再是創(chuàng)建的Fn實(shí)例,而是自己返回的對(duì)象。
let f1 = new Fn(10, 20);
“實(shí)例 instanceof 構(gòu)造函數(shù)” :檢測(cè)當(dāng)前實(shí)例是否屬于這個(gè)類。
console.log(f1 instanceof Fn); //false
🌰 例如三:函數(shù)擴(kuò)展
- function Fn(x, y) {
- let total = x + y;
- this.x = x;
- this.y = y;
- this.say = function say() {
- console.log(`SAY:${total}`);
- };
- }
- let f1 = new Fn(10, 20);
- let f2 = new Fn;
畫圖分析:( 有圖有真相 )
結(jié)果:
- console.log(f1 === f2); //false
- console.log(f1.say === f2.say); //false
Fn VS Fn()
1) Fn代表的是函數(shù)本身(堆內(nèi)存 -> ƒ Fn(x, y) {...});
2) Fn()是把函數(shù)執(zhí)行,獲取其返回值;
new Fn VS new Fn()
都是把Fn執(zhí)行了,只是第一個(gè)沒(méi)有傳遞實(shí)參,第二個(gè)可以傳遞實(shí)參而已。
1) new Fn; 運(yùn)算優(yōu)先級(jí)是18(無(wú)參數(shù)列表new)
2) new Fn(); 運(yùn)算符優(yōu)先級(jí)是19(有參數(shù)列表new)
* 檢測(cè)一個(gè)屬性是否為當(dāng)前對(duì)象的成員
1)屬性名 in 對(duì)象:不論是私有屬性還是公有的屬性,只要有就是true;
2)對(duì)象.hasOwnProperty(屬性名):必須是對(duì)象的私有屬性,結(jié)果才是true;
說(shuō)明:自己擴(kuò)展一個(gè)方法 hasPubProperty(對(duì)象,屬性名):檢測(cè)當(dāng)前屬性是否屬于對(duì)象的公有屬性(特點(diǎn):必須有這個(gè)屬性,而且不是私有的)(需要擴(kuò)展)
屬性和變量有什么共同點(diǎn):
沒(méi)有。屬性是堆內(nèi)存的成員,變量是棧內(nèi)存或者上下文當(dāng)中變量。
- console.log('say' in f1); //true
- console.log('toString' in f1); //true
- console.log('total' in f1); //false
- console.log(f1.hasOwnProperty('say')); //true
- console.log(f1.hasOwnProperty('toString')); //false
- console.log(f1.hasOwnProperty('total')); //false
🌰 例如四:函數(shù)擴(kuò)展
- Object.prototype.AA = '前端學(xué)苑';
- let obj = {
- name: 'xxx',
- age: 11,
- 0: 100,
- [Symbol('AA')]: 200,
- [Symbol.toPrimitive]: function () {
- return 0;
- }
- };
基于“for...in”循環(huán)遍歷對(duì)象
1) 優(yōu)先遍歷數(shù)字屬性;
2) 不會(huì)遍歷到Symbol屬性;
3) 會(huì)把自己擴(kuò)展到“類原型”上的公共屬性方法也遍歷到「可枚舉的」
- for (let key in obj) {
- // 在遍歷過(guò)程中,遍歷到公共屬性,則停止遍歷:
- // 因?yàn)?span id="owocgsc" class="keyword">for...in遍歷的本意就是只遍歷私有的屬性即可
- if (!obj.hasOwnProperty(key)) break;
- console.log(key);
- }
- let keys = [
- ...Object.keys(obj),
- ...Object.getOwnPropertySymbols(obj)
- ];
- keys.forEach(key => {
- console.log(`屬性名:${String(key)},屬性值:${obj[key]}`);
- });
說(shuō)明:
1)Object.keys(obj):獲取當(dāng)前對(duì)象所有非Symbol的私有屬性「數(shù)組」 =>Object.getOwnPropertyNames.
2)Object.getOwnPropertySymbols(obj):獲取對(duì)象所有的Symbol私有屬性「數(shù)組」.
面向?qū)ο笾械脑秃驮玩?/span>
* 核心答案 | 基礎(chǔ)知識(shí)要夯實(shí)
1、函數(shù)數(shù)據(jù)類型
1)普通函數(shù)
2)箭頭函數(shù)
3)生成器函數(shù)
4)構(gòu)造函數(shù)(類)
2、對(duì)象數(shù)據(jù)類型
1)普通對(duì)象/數(shù)組對(duì)象/正則對(duì)象/日期對(duì)象...
2)實(shí)例也是對(duì)象數(shù)據(jù)類型的(排除7種原始值類型)
3)prototype/__proto__原型屬性值也是對(duì)象(排除Function.prototype)
3、大部分函數(shù)(重點(diǎn)是構(gòu)造函數(shù)) 都內(nèi)置一個(gè)prototype(原型「顯式原型」)的屬性,屬性值是一個(gè)對(duì)象,對(duì)象中存儲(chǔ)的屬性和方法,是供當(dāng)前類所屬實(shí)例,調(diào)用的“公共”的屬性和方法
1)箭頭函數(shù)是沒(méi)有prototype屬性的;
2)在原型對(duì)象上有一個(gè)內(nèi)置的屬性 constructor(構(gòu)造器),屬性值是當(dāng)前函數(shù)本身;
4、每一個(gè)對(duì)象都內(nèi)置一個(gè)__proto__(原型鏈「隱式原型」)的屬性,屬性值指向自己所屬類的原型prototype對(duì)象。
1)Object.prototype這個(gè)對(duì)象的__proto__值是null,因?yàn)镺bject是所有對(duì)象的“基類”
只繪制“堆內(nèi)存”,畫圖分析:( 有圖有真相 )
解析說(shuō)明:
每一個(gè)數(shù)組都是Array類的實(shí)例,所以每一個(gè)數(shù)組的_proto_一定指向Array.prototype;
每一個(gè)對(duì)象都是Object類的實(shí)例,所以Array.prototype對(duì)象中的_proto_屬性指向Object.prototype;
原型鏈的查找機(jī)制
arr[1]或者 arr.push() 再或者 arr.hasOwnProperty()…
1)首先查找當(dāng)前實(shí)例對(duì)象的私有屬性,私有中有,獲取就是私有的;
2)如果私有中沒(méi)有,則瀏覽器默認(rèn)基于_proto_找其所屬類原型(prototype)上的公共屬性和方法;
3)如果還找不到,則基于原型對(duì)象上的_proto_繼續(xù)向上查找 … 直到找到Object.prototype為止。最終到null。
例如:arr.push <=> arr._proto_.push <=> Array.prototype.push
1)找到的方法都是相同的;
2)區(qū)別是方法執(zhí)行時(shí)候,里面的this 不同;
(1)arr.push() arr首先基于原型鏈查找機(jī)制,找到Array.prototype上的push 方法,并且把方法執(zhí)行,方法中的this -> arr;
(2)arr._proto_.push() 直接跳過(guò)私有屬性的查找,找公共的,方法執(zhí)行的時(shí)候,方法中的this -> arr._proto_;
Array.prototype.push() this -> Array.prototype
3)_proto_ 在IE瀏覽器中進(jìn)行訪問(wèn)。(那如果代碼需要用到_proto_ ,怎么在IE中用呢? 結(jié)果:不能用。)
arr.hasOwnProperty('push') -> false
Array.prototype.hasOwnProperty('push') -> true
公有還是私有屬性,它是有參照物的
1)存儲(chǔ)在自己的堆內(nèi)存中的屬性是 “私有的”;
2)基于_proto_查找到的屬性和方法有“公有的”;
每一個(gè)數(shù)組“即是數(shù)組也是對(duì)象”,因?yàn)樗鼈兛梢哉{(diào)用Array.prototype和Object.prototype的屬性和方法。
* 構(gòu)造函數(shù)、原型與實(shí)例之間的關(guān)系
關(guān)系解析說(shuō)明:
每個(gè)構(gòu)造函數(shù)都有一個(gè)prototype屬性指向它的原型對(duì)象,原型對(duì)象的constructor 指向構(gòu)造函數(shù),通過(guò)new 構(gòu)造函數(shù) 生成實(shí)例,實(shí)例的__proto__屬性指向原型對(duì)象。
🌰 原型與原型鏈
- function Fn() {
- this.x = 100;
- this.y = 200;
- this.getX = function () {
- console.log(this.x);
- }
- }
- Fn.prototype.getX = function () {
- console.log(this.x);
- };
- Fn.prototype.getY = function () {
- console.log(this.y);
- };
- let f1 = new Fn;
- let f2 = new Fn;
- console.log(f1.getX === f2.getX); // false 「都是私有的方法」
- console.log(f1.getY === f2.getY); // true 「都是公共的方法」
- console.log(f1.__proto__.getY === Fn.prototype.getY); // true
- console.log(f1.__proto__.getX === f2.getX); // false
- console.log(f1.getX === Fn.prototype.getX); // false
- console.log(f1.constructor); // fn
- console.log(Fn.prototype.__proto__.constructor); // Object
- f1.getX();
- f1.__proto__.getX();
- f2.getY();
- Fn.prototype.getY();
畫圖分析:( 有圖有真相 )
解析說(shuō)明:
1)先確定執(zhí)行哪個(gè)方法「私有|公有」;
2)再確定執(zhí)行方法中的this;
3)最后方法執(zhí)行,計(jì)算機(jī)需要的結(jié)果即可;
f1.getX()
執(zhí)行的私有方法,this -> f1
console.log(f1.x) => 100
f1._proto_.getX()
執(zhí)行的公有方法,this -> f1._proto_
console.log(f1._proto_.x) => undefined
f2.getY()
執(zhí)行的公有方法,this -> f2
console.log(f2.y) => 200
Fn.prototype.getY()
執(zhí)行的公有方法,this -> Fn.prototype
console.log(Fn.prototype.y) => undefined
重寫內(nèi)置new以及基于內(nèi)置類原型擴(kuò)展方法
* 核心答案 | 基礎(chǔ)知識(shí)要夯實(shí)
🌰 * 1、new執(zhí)行的原理 - 面試題( 面試常問(wèn) )
- function Dog(name) {
- this.name = name;
- }
- Dog.prototype.bark = function () {
- console.log('wangwang');
- }
- Dog.prototype.sayName = function () {
- console.log('my name is ' + this.name);
- }
- /*
- let sanmao = new Dog('三毛');
- sanmao.sayName();
- sanmao.bark();
- */
- function _new() {
- //=>完成你的代碼
- }
- let sanmao = _new(Dog, '三毛');
- sanmao.bark(); //=>"wangwang"
- sanmao.sayName(); //=>"my name is 三毛"
- console.log(sanmao instanceof Dog); //=>true
解決方法一( __proto__ 在IE瀏覽器兼容很差,不建議使用 )
- function _new(Ctor, ...params) {
- // Ctor->Dog params->['三毛']
- let obj = {};
- obj.__proto__ = Ctor.prototype;
- // this->指向創(chuàng)建的實(shí)例對(duì)象 基于call方法改變即可
- let result = Ctor.call(obj, ...params);
- if (/^(object|function)$/.test(typeof result)) return result;
- return obj;
- }
解析說(shuō)明:
1、創(chuàng)建一個(gè)實(shí)例對(duì)象 實(shí)例對(duì)象.__proto__===所屬類.prototype;
2、會(huì)把構(gòu)造函數(shù)當(dāng)做普通函數(shù)執(zhí)行「私有上下文、作用域鏈、初始THIS、形參賦值...」;
3、觀察函數(shù)執(zhí)行的返回值,如果沒(méi)有返回值或者返回的是基本數(shù)據(jù)類型值,默認(rèn)返回的都是實(shí)例對(duì)象,否則以自己返回的值為主。
Object.create([pro]):創(chuàng)建一個(gè)空對(duì)象,把[pro]作為當(dāng)前創(chuàng)建空對(duì)象的__proto__的指向(把[pro]作為當(dāng)前創(chuàng)建空對(duì)象的原型)。
1、[pro]可以傳遞null或者一個(gè)對(duì)象;
2、如果傳遞的是null,則當(dāng)前空對(duì)象不具備__proto__的屬性,也就是不屬于任何類的實(shí)例。
🌰 例如:
- let pro = {
- A: 10,
- B: 20
- };
- //Uncaught TypeError: Object prototype may only be an Object or null: undefined
- console.log(Object.create());
- console.log(Object.create(null));
解決方法二( 在IE6,7,8瀏覽器不兼容 )
- function _new(Ctor, ...params) {
- let obj = Object.create(Ctor.prototype);
- let result = Ctor.call(obj, ...params);
- if (/^(object|function)$/.test(typeof result)) return result;
- return obj;
- }
解決方法三 ( 兼容性比較好 )
- // 重寫的方法只考慮pro傳遞的是一個(gè)對(duì)象
- Object.create = function (pro) {
- function Proxy() {}
- Proxy.prototype = pro;
- return new Proxy;
- };
- function _new(Ctor) {
- // 獲取除第一個(gè)實(shí)參以外,剩余傳遞的參數(shù)信息,以數(shù)組的形式保存到params中
- var params = [].slice.call(arguments, 1);
- // Object.create兼容IE低版本瀏覽器,需要改寫
- var obj = Object.create(Ctor.prototype);
- // 基于apply既可以改變this,也可以把數(shù)組中的每一項(xiàng)傳遞給函數(shù)
- var result = Ctor.apply(obj, params);
- if (/^(object|function)$/.test(typeof result)) return result;
- return obj;
- }
* 2、擴(kuò)展內(nèi)置類原型上的方法
1、調(diào)用的時(shí)候更加方便;
2、也更好的實(shí)現(xiàn)鏈?zhǔn)秸{(diào)用;
* 注意:自己編寫的方法會(huì)覆蓋內(nèi)置的方法,所以自己命名的時(shí)候需要注意,一般都是設(shè)置前綴,例如:myUnique。
🌰 常用數(shù)組去重方法
- function unique(arr) {
- // 首先基于Set結(jié)構(gòu)去重,最后轉(zhuǎn)換為數(shù)組
- let result = new Set(arr);
- result = Array.from(result);
- return result;
- }
- let arr = [1, 2, 3, 2, 3, 4, 2, 3, 4, 2, 1, 2, 3, 4, 5, 3, 4];
- let result = unique(arr);
- console.log(result);
🌰 先去重,再排序 - 面試題( 面試常問(wèn) )
- Array.prototype.unique = function unique() {
- // this->arr 一般是當(dāng)前操作類的實(shí)例
- let result = new Set(this);
- result = Array.from(result);
- return result; //返回的結(jié)果還是一個(gè)數(shù)組,則可以繼續(xù)調(diào)用數(shù)組的其它方法 ->“鏈?zhǔn)秸{(diào)用”
- };
- // 先去重,再排序
- // + sort是Array.prototype上的方法,所以數(shù)組可以直接調(diào)用
- let arr = [1, 2, 3, 2, 3, 4, 2, 3, 4, 2, 1, 2, 3, 4, 5, 3, 4];
- let result = arr.unique().sort((a, b) => a - b);
- console.log(arr, result);
THIS情況匯總及CALL、APPLY、BIND的應(yīng)用
* 核心答案 | 基礎(chǔ)知識(shí)要夯實(shí)
1、THIS的幾種情況
1)事件綁定;
2)函數(shù)執(zhí)行:1)自執(zhí)行函數(shù) 2)回調(diào)函數(shù);
3)構(gòu)造函數(shù)執(zhí)行;
4)基于call /apply /bind 改變函數(shù)中的this;
5)箭頭函數(shù)中沒(méi)有自己的this,所用到的this是使用其上下文中的;
說(shuō)明:Function.prototype -> call/apply/bind 所有的函數(shù)都可以調(diào)取這三個(gè)辦法。
- Function.prototype.call = function call(context) {
- // this->fn
- // context->obj
- // ...
- };
🌰 基于call /apply /bind 改變函數(shù)中的this
- window.name = 'WINDOW';
- let obj = {
- name: '前端學(xué)苑',
- age: 2
- };
- function fn(x, y) {
- console.log(this, x + y);
- }
- fn(); //this->window
- obj.fn(); //Uncaught TypeError: obj.fn is not a function
- fn.call(obj); //this->obj
- fn.call(obj, 10, 20); //this->obj x->10 y->20
- fn.call(); //this->window 嚴(yán)格模式下undefined
- fn.call(null); //this->window 嚴(yán)格模式下null 「?jìng)鬟f的是undefiend也是如此」
- fn.call(10, 20); //this->10「對(duì)象」 x->20 y->undefined
解析說(shuō)明:
fn.call(obj);
底層處理方式:fn先基于__proto__找到Function.prototype.call,把call方法執(zhí)行的時(shí)候,call方法內(nèi)部實(shí)現(xiàn)了一些功能:會(huì)把fn執(zhí)行,并且讓fn中的this變?yōu)榈谝粋€(gè)實(shí)參值。
* apply的作用和細(xì)節(jié)上和call一樣,只有一個(gè)區(qū)別:傳遞給函數(shù)實(shí)參的方式不一樣。
- fn.call(obj, 10, 20);
- fn.apply(obj, [10, 20]);
最后結(jié)果和call是一樣的,只不過(guò)apply方法執(zhí)行的時(shí)候要求:傳遞給函數(shù)的實(shí)參信息都要放置在一個(gè)數(shù)組中,但是apply內(nèi)部也會(huì)向call方法一樣,把這些實(shí)參信息一項(xiàng)項(xiàng)的傳遞給函數(shù)。
🌰 需求:獲取數(shù)組中的最大值
- let arr = [10, 30, 15, 36, 23];
* 方法一:先排序
- arr.sort(function (a, b) {
- return b - a;
- });
- let max = arr[0];
- console.log('數(shù)組中的最大值是:' + max);
* 方法二:假設(shè)法
第一種方法:
- let max = arr[0];
- for (let i = 1; i < arr.length; i++) {
- let item = arr[i];
- if (item > max) {
- max = item;
- }
- }
- console.log('數(shù)組中的最大值是:' + max);
第二種方法:
- let max = arr.reduce((result, item) => {
- return item > result ? item : result;
- });
- console.log('數(shù)組中的最大值是:' + max);
* 方法三:借用Math.max
第一種方法:
- Math.max(10, 30, 15, 36, 23) ->36 獲取一堆數(shù)中的最大值
- Math.max([10, 30, 15, 36, 23]) ->NaN 傳遞一個(gè)數(shù)組是不行的
第二種方法:ES6展開(kāi)運(yùn)算符
- let max = Math.max(...arr);
- console.log('數(shù)組中的最大值是:' + max);
第三種方法:基于apply的特點(diǎn)
- let max = Math.max.apply(null, arr);
- console.log('數(shù)組中的最大值是:' + max);
第四種方法:字符串拼接成為最終想要的表達(dá)式
- let str = `Math.max(${arr})`;
- let max = eval(str);
- console.log('數(shù)組中的最大值是:' + max);
🌰 需求:把類數(shù)組集合轉(zhuǎn)換為數(shù)組集合
重寫內(nèi)置的slice,實(shí)現(xiàn)淺克??;
- Array.prototype.slice = function slice() {
- // 重寫內(nèi)置的slice,實(shí)現(xiàn)淺克隆
- // this->ary
- let arr = [];
- for (let i = 0; i < this.length; i++) {
- let item = this[i];
- arr.push(item);
- }
- return arr;
- };
- let ary = [10, 20, 30];
- let newAry = ary.slice(); //不傳遞或者傳遞0 -> 數(shù)組的淺克隆
- console.log(newAry, newAry === ary);
畫圖分析:( 有圖有真相 )
區(qū)別在于:
1、內(nèi)置代碼中用的是this,自己寫的代碼中用的是argument。
2、如果我們可以 讓內(nèi)置的slice執(zhí)行,并且把方法中的 this改變?yōu)閍rguments -> 這樣其實(shí)就是把類數(shù)組集合克?。ㄞD(zhuǎn)換)為數(shù)組。
3、讓內(nèi)置slice執(zhí)行:找到slice,加小括號(hào)一執(zhí)行即可。
1)Array.prototype.slice() 2)[].slice()
改變方法中的this
1)call 2)apply
4、[].slice.call(arguments) 把類數(shù)組轉(zhuǎn)換為數(shù)組
重要:數(shù)組中大部分方法,都可以基于這樣的原理(改變this),實(shí)現(xiàn)類數(shù)組的借用。原因:類數(shù)組除了不是Array的實(shí)例,和數(shù)組的結(jié)果是一致的,所以操作數(shù)組的一些代碼(類似于循環(huán)等操作)也一定適用于類數(shù)組,所以可以實(shí)現(xiàn)方法的借用。
求和第一種方法:
- function sum() {
- // arguments:實(shí)參集合,它是一個(gè)類數(shù)組,不是Array的實(shí)例,所以不能直接調(diào)用Array.prototype上的方法,但是結(jié)構(gòu)和數(shù)組非常的相似,都是索引+length
- // 第一種方法
- let arr = [];
- for (let i = 0; i < arguments.length; i++) {
- let item = arguments[i];
- arr.push(item);
- }
- // 第二種方法
- // let arr = [].slice.call(arguments);
- return arr.reduce((result, item) => item + result);
- }
- let total = sum(10, 20, 30, 40);
求和第二種方法,ES6實(shí)現(xiàn):
- function sum(...arr) {
- // 第一種方法:... arr 基于剩余運(yùn)算符獲取的實(shí)參集合本身就是一個(gè)數(shù)組
- // 第二種方法:Array.from:可以把一個(gè)類數(shù)組(或者Set)轉(zhuǎn)換為數(shù)組
- // let arr = Array.from(arguments);
- // 第三種方法:基于展開(kāi)運(yùn)算符把類數(shù)組中的每一項(xiàng)拿出來(lái),分別賦值給數(shù)組
- // let arr = [...arguments];
- return arr.reduce((result, item) => item + result);
- }
- let total = sum(10, 20, 30, 40);
- console.log(total); // 結(jié)果:100
🌰 this指向的例子
- let obj = {
- name: '前端學(xué)苑',
- age: 11
- };
- function fn(x, y) {
- console.log(this, x, y);
- }
1、操作一:
- document.body.onclick = fn;
分析:
1)事件綁定的時(shí)候方法是沒(méi)有執(zhí)行的,只有事件觸發(fā),瀏覽器會(huì)幫助我們把方法執(zhí)行;
2)this->body;
3)x->MouseEvent 事件對(duì)象「瀏覽器不僅幫助我們把方法執(zhí)行,而且還把存儲(chǔ)當(dāng)前操作的信息的事件對(duì)象傳遞給函數(shù)」;
4)y->undefined;
2、操作二:
- setTimeout(fn, 1000);
分析:
1)設(shè)置一個(gè)定時(shí)器(此時(shí)綁定的函數(shù)沒(méi)有執(zhí)行,此時(shí)只是綁定一個(gè)方法),1000MS后,瀏覽器會(huì)幫助我們把fn執(zhí)行;
2)this->window;
3)x->undefined;
4)y->undefined;
我們期望:不論是事件觸發(fā),還是定時(shí)器到時(shí)間,執(zhí)行對(duì)應(yīng)的方法時(shí),可以改變方法中的this,以及給方法傳遞實(shí)參信息。
1、“立即處理的思想”
直接下屬這種操作辦法是不可以的:call/apply在處理的時(shí)候,會(huì)把函數(shù)立即執(zhí)行,也就是在事件綁定或者設(shè)置定時(shí)器的時(shí)候,fn就執(zhí)行了,而不是等待事件觸發(fā)或者定時(shí)器到時(shí)間后再執(zhí)行 “立即處理的思想”。
代碼如下:
- document.body.onclick = fn.call(obj, 10, 20);
- setTimeout(fn.call(obj, 10, 20), 1000);
2、“預(yù)先處理思想「柯理化函數(shù)」”
我們綁定方法的時(shí)候(不論是事件綁定還是設(shè)置定時(shí)器),先綁定一個(gè)匿名函數(shù),事件觸發(fā)或者達(dá)到時(shí)間,先把匿名函數(shù)執(zhí)行,在執(zhí)行匿名函數(shù)的時(shí)候,再把我們需要執(zhí)行的fn執(zhí)行,此時(shí)就可以基于call/apply改變this和參數(shù)信息了。
代碼如下:
- document.body.onclick = function (ev) {
- //this->body
- fn.call(obj, 10, 20, ev);
- };
- setTimeout(function () {
- //this->window
- fn.call(obj, 10, 20);
- }, 1000);
bind相當(dāng)于call/apply來(lái)講,并不會(huì)把函數(shù)立即執(zhí)行,只是實(shí)現(xiàn)處理了要改變的this和參數(shù),一切的執(zhí)行還是按照原有的時(shí)間或者觸發(fā)節(jié)點(diǎn)進(jìn)行。
代碼如下:
- document.body.onclick = fn.bind(obj, 10, 20);
- setTimeout(fn.bind(obj, 10, 20), 1000);
* 箭頭函數(shù)沒(méi)有自己的this
🌰 實(shí)例一:
- let obj = {
- name: '前端學(xué)苑',
- age: 11,
- fn: function () {
- // this->obj
- let that = this;
- return function () {
- // this->window
- // 如果需要改變obj.name,可以用that替換this
- that.name = 'FE2020';
- console.log(this);
- };
- }
- };
- let f = obj.fn();
- f();
🌰 實(shí)例二:
- let obj = {
- name: '前端學(xué)苑',
- age: 11,
- fn: function () {
- // this->obj
- return () => {
- this.name = 'FE2020';
- console.log(this); // {name:'FE2020', age: 11, fn:f}
- };
- }
- };
- let f = obj.fn();
- f.call(100);
說(shuō)明:
箭頭函數(shù)沒(méi)有this(方法執(zhí)行的時(shí)候不存在初始this這一項(xiàng)操作),所以基于call/apply操作它都是無(wú)用的,沒(méi)有this。






