第一篇分爲6個部分javascript
this的指向問php
閉包java
call、apply、bind詳解es6
prototype、__proto__和constructor的關係面試
高階函數chrome
ES6中類和繼承與ES5的區別express
一.This的指向問題編程
this的判斷只要記住一點,就是在執行的時候動態綁定的(es6的箭頭函數除外)數組
01. new出來的實例,this指向返回的實例。瀏覽器
02. 有對象調用,this指向這個對象。
03. call,apply 綁定,this指向綁定的對象。
04. 匿名函數或者全局調用函數,this指向window。
05. 事件綁定函數,this指向綁定的對象(event.currentTarget)。
06. ES6箭頭函數裏this的指向就是上下文裏對象this指向,偶爾沒有上下文對象,this就指向window。
案例詳解:
var a = 1;var o = { a: 10, b: { a: 12, fn: function () { console.log(this.a); } }, globalFun: (function () { // "use strict" // 若是這裏把上句註釋打開開啓嚴格模式就會報錯 return this.a; })(), arrowFun: function () { //箭頭函數 setTimeout(() => { console.log(this); // this ==> o }); //普通函數 setTimeout(function () { console.log(this); // this ==> window }); }}o.b.fn(); //this=>b,輸出12console.log(o.globalFun); //this=>window,輸出1o.b.fn.call(o); //this=>o,輸出10var f = new o.b.fn(); //this=>f,輸出undefinedo.arrowFun();var testDiv = document.createElement("div");testDiv.innerHTML = "測試";testDiv.a = "asd";document.body.appendChild(testDiv);testDiv.addEventListener("click", o.b.fn) //this=>testDiv,輸出asd複製代碼
知識點補充:
1.在嚴格版中的默認的this再也不是window,而是undefined
"use strict"function A() { this.a = 10;}A();console.log(a); //報錯複製代碼
2. 當new一個有返回值的function時,this的指向問題
當方法返回的值是引用類型時,this指向此對象
緣由在於:
被調用的函數沒有顯式的 return 表達式(僅限於返回對象),
則隱式的會返回 this 對象 - 也就是新建立的對象
function A() { this.n = 10; return { n: 11 };}var a = new A();console.log(a.n); //11複製代碼
3.new幹了什麼,new是一種語法糖
01.建立臨時對象
02.綁定原型
03.執行構造函數
04.原函數返回非引用類型時,return臨時對象(this)。
實現一下:
方案1:
function A() { this.n = 10;}var _new = function (fn) { var temp = Object.create(fn.prototype); var rv = fn.apply(temp, [].slice.call(arguments, 1)); return (typeof rv === "object" && rv) || temp;}var a = _new(A);複製代碼
方案2:
var _new = function (fn) { var temp = {}; temp.__proto__ = fn.prototype; var rv = fn.apply(temp, [].slice.call(arguments, 1)); return (typeof rv === "object" && rv) || temp;}複製代碼
二.閉包
優勢:
01.緩存
02.面向對象中的對象
03.實現封裝,防止變量跑到外層做用域中,發生命名衝突
04.訪問到非自身的做用於內的變量
缺點:
01.常駐內存,會增大內存使用量,使用不當很容易形成內存泄露
02.使用閉包時,會涉及到跨做用域訪問,每次訪問都會致使性能損失
實例1(緩存)
var db = (function () { var data = {}; return function (key, val) { if (val === undefined) { return data[key] } // get else { return data[key] = val } // set }})();db('x'); // 返回 undefineddb('x', 1); // 設置data['x']爲1db('x'); // 返回 1複製代碼
實例2(封裝和麪向對象,模塊)
var person = function () { var name = "default"; return { getName: function () { return name; }, setName: function (newName) { name = newName; } }}();console.log(person.name); //直接訪問,結果爲undefined console.log(person.getName());person.setName("abruzzi");console.log(person.getName());複製代碼
實例2.1(擴展上一個例子,模塊管理器)
var MyModules = (function Manager() { var modules = {}; function define(name, deps, impl) { for (var i = 0; i < deps.length; i++) { deps[i] = modules[deps[i]]; } modules[name] = impl.apply(impl, deps); } function get(name) { return modules[name]; } return { define: define, get: get };})();MyModules.define("bar", [], function () { function hello(who) { return "Let me introduce: " + who; } return { hello: hello };});MyModules.define("foo", ["bar"], function (bar) { var hungry = "hippo"; function awesome() { console.log(bar.hello(hungry).toUpperCase()); } return { awesome: awesome };});var bar = MyModules.get("bar");var foo = MyModules.get("foo");console.log(bar.hello("hippo")); // Let me introduce: hippo foo.awesome(); // LET ME INTRODUCE: HIPPO複製代碼
實例3(回調函數)
在定時器、事件監聽器、 Ajax 請求、跨窗口通訊、Web Workers 或者任何其餘的異步(或者同步)任務中,只要使 用了回調函數,就是閉包
for (var i = 1; i <= 5; i++) { (function (j) { setTimeout(function timer() { console.log(j); }, j * 1000); })(i);}複製代碼
三.Call、apply、bind
apply和call,bind都是爲了改變某個函數運行時的上下文而存在的(就是爲了改變函數內部this的指向);
apply和call二者的區別:
若是使用apply或call方法,那麼this指向他們的第一個參數,apply的第二個參數是一個參數數組,call的第二個及其之後的參數都是數組裏面的元素
var numbers = [5, 458, 120, -215];var maxNum = Math.max.apply(Math, numbers), var maxNum1 = Math.max.call(Math, 5, 458, 120, -215);Math.max(5, 458, 120, -215)複製代碼
bind和apply、call二者的區別:
bind不會當即調用(新的方法),其餘兩個會當即調用
var obj = { b: 10};var a = (function () { console.log(this.b)}).bind(obj);a();複製代碼
進階知識:
1.call,apply不只能夠改變函數的上下文環境,還可讓綁定的對象擁有目標this上的屬性
function A() { this.a = 10; this.test = function () {}}var obj = {};A.call(obj);console.log(obj);// obj 此時擁有A中的屬性複製代碼
2.call,apply實現bind功能(IE8下)
Function.prototype.bind = function (oThis) { var aArgs = Array.prototype.slice.call(arguments, 1), fToBind = this, fNOP = function () {}, fBound = function () { return fToBind.apply(this instanceof fNOP ? this : oThis, aArgs.concat(Array.prototype.slice.call(arguments))); }; if (this.prototype) { fNOP.prototype = this.prototype; } fBound.prototype = new fNOP(); return fBound;};複製代碼
四.prototype、__proto__和constructor的關係
先了解什麼是本地對象和內置對象
本地: Object、Function、Array、String、Boolean、
Number、Date、RegExp、Error、EvalErrorRangeError、
ReferenceError、SyntaxError、TypeError、URIError複製代碼
內置: Global 、Math 、JSON 、arguments複製代碼
概念:
prototype是函數的屬性,這個屬性是一個指針,指向一個對象,它表明了對象的原型(Function.prototype函數對象是個例外,沒有prototype屬性),
用處:擴展原型功能,new和繼承時使用
__proto__是一個對象擁有的內置屬性(prototype是函數的內置屬性,__proto__是對象的內置屬性),用chrome和FF均可以訪問到對象的__proto__屬性,IE10-沒有,規範使用Object.
getPrototypeOf()獲取原型,
用處:內部查找、維持原型鏈
constructor 實例自動擁有,指向其構造器,
用處:保持對其構造函數的引用
01.全部構造器/函數的__proto__都指向Function.prototype,它是一個空函數(Empty function)
console.log(Number.__proto__ === Function.prototype) // true console.log(Boolean.__proto__ === Function.prototype) // true console.log(String.__proto__ === Function.prototype) // true console.log(Object.__proto__ === Function.prototype) // true console.log(Function.__proto__ === Function.prototype) // true console.log(Array.__proto__ === Function.prototype) // true console.log(RegExp.__proto__ === Function.prototype) // true console.log(Error.__proto__ === Function.prototype) // true console.log(Date.__proto__ === Function.prototype) // true 複製代碼
02.全部實例的__proto__都指向其構造函數的prototype
function A() {}var a = new A();console.log(a.__proto__ === A.prototype) // truevar a = 1;console.log(a.__proto__ === Number.prototype) // true複製代碼
03.全部的構造器/函數的constructor都指向Function,函數都是Function的實例.實例的constructor指向該構造器函數,該構造函數的prototype的constructor屬性, 又等於該構造器函數.
function A() {}var a = new A();console.log(A.constructor === Function); // trueconsole.log(a.constructor === A.prototype.constructor) // trueconsole.log(a.constructor === A) // true//a.constructor === A.prototype.constructor === A複製代碼
幾種特殊的狀況:
1.Math,JSON等內置對象的構造函數等於Object
console.log(Math.constructor === Object,JSON.constructor === Object)複製代碼
2.Function.constructor等於其自身Function
console.log(Function.constructor === Function)複製代碼
3.Function.prototype的類型的」function」而且與其__proto__的值恆等
console.log(typeof Function.prototype === "function");
console.log(Function.prototype === Function.__proto__);複製代碼
4.Function.prototype沒有prototype屬性
console.log(Function.prototype.prototype === undefined);複製代碼
5.Function.prototype.__proto__指向Object.prototype
console.log(Function.prototype.__proto__ === Object.prototype);複製代碼
6. Object.prototype.__proto__爲null,原型鏈終結於此
console.log(Object.prototype.__proto__ === null)複製代碼
7.只有本地對象和自定義的函數纔有prototype屬性
console.log(Array.prototype.push.prototype === undefined);複製代碼
實例講解(繼承)
es5:
function Super() { this.type = "super";}Super.prototype.say = function () { console.log(this.type)};function Sub() { Super.call(this); this.type = "sub";}Sub.prototype = new Super();Sub.prototype.constructor = Sub;var sub = new Sub();sub.say();複製代碼
下面我畫了一張原型的圖但願能夠幫助你們理解其中的奧妙
五.高階函數
高階函數就是能夠把函數做爲參數,或者是將函數做爲返回值的函數
1) 回調函數
例如:ES5新增數組的forEach,map,filter,every,some,reduce
用reduce方法舉例(reduce()能夠實現一個累加器的功能,將數組的每一個值(從左到右)將其下降到一個值):
//實現數組中相同的值出現的次數:複製代碼
var arr = ["apple", "orange", "apple", "orange", "pear", "orange"];function getWordCnt() { return arr.reduce(function (prev, next) { prev[next] = (prev[next] + 1) || 1; return prev; }, {});}console.log(getWordCnt())複製代碼
2) AOP
AOP(面向切面編程)的主要做用是把一些跟核心業務邏輯模塊無關的功能抽離出來,這些跟業務邏輯無關的功能一般包括日誌統計、安全控制、異常處理等。把這些功能抽離出來以後,再經過「動態織入」的方式摻入業務邏輯模塊中。這樣作的好處首先是能夠保持業務邏輯模塊的純淨和高內聚性,其次是能夠很方便地複用日誌統計等功能模塊.
Function.prototype.before = function (beforefn) { var __self = this; return function () { beforefn.apply(this, arguments); return __self.apply(this, arguments); }};Function.prototype.after = function (afterfn) { var __self = this; return function () { var ret = __self.apply(this, arguments); afterfn.apply(this, arguments); return ret; }};複製代碼
用法:
在一個方法以後調用另外一個方法:
var a = function () { console.log("2")};a = a.before(function () { console.log("1")}).after(function () { console.log("3")});a();複製代碼
防止window.onload被二次覆蓋:
window.onload = function () { console.log(1)};window.onload = (window.onload || function () {}).after(function () { console.log(2)});複製代碼
3) currying
柯里化(英語:Currying),又譯爲卡瑞化或加里化,是把接受多個參數的函數變換成接受一個單一參數(最初函數的第一個參數)的函數,而且返回接受餘下的參數並且返回結果的新函數的技術
function currying(fn) { var slice = Array.prototype.slice, __args = slice.call(arguments, 1); return function () { var __inargs = slice.call(arguments); return fn.apply(this, __args.concat(__inargs)); };}複製代碼
用法
提升適用性
function Ajax() { this.xhr = new XMLHttpRequest();}Ajax.prototype.open = function (type, url, data, callback) { this.onload = function () { callback(this.xhr.responseText, this.xhr.status, this.xhr); } this.xhr.open(type, url, data.async); this.xhr.send(data.paras);}'get post'.split(' ').forEach(function (mt) { Ajax.prototype[mt] = currying(Ajax.prototype.open, mt);});var xhr = new Ajax();xhr.get('/a.php', {}, function (datas) {});var xhr1 = new Ajax();xhr1.post('/b.php', {}, function (datas) {});複製代碼
2.延遲執行
var add = function () { var _this = this, _args = arguments return function () { if (!arguments.length) { var sum = 0; for (var i = 0, c; c = _args[i++];) sum += c return sum } else { Array.prototype.push.apply(_args, arguments); return arguments.callee } }}add(1)(2)(3)(4)(); //10複製代碼
3.固定易變因素
bind函數用以固定this這個易變對象(想不到吧bind也是curring化的一種是應用)
var obj = { b: 10};var a = (function () { console.log(this.b)}).bind(obj);a();複製代碼
4) unCurrying(反柯里化)
在JavaScript中,當咱們調用對象的某個方法時,其實不用去關心該對象本來是否被設計爲擁有這個方法,這是動態類型語言的特色,也是常說的鴨子類型思想。
同理,一個對象也未必只能使用它自身的方法,那麼有什麼辦法可讓對象去借用一個本來不屬於它的方法呢?
答案對於咱們來講很簡單,call和apply均可以完成這個需求,由於用call和apply能夠把任意對象看成this傳入某個方法,這樣一來,方法中用到this的地方就再也不侷限於原來規定的對象,而是加以泛化並獲得更廣的適用性。
而uncurrying的目的是將泛化this的過程提取出來,將fn.call或者fn.apply抽象成通用的函數。
代碼實現:
Function.prototype.unCurrying = function () { var that = this; return function () { return Function.prototype.call.apply(that, arguments); }}複製代碼
應用1:
var push = Array.prototype.push.unCurrying(), obj = {};push(obj, 'first', 'two');console.log(obj); //{0:’first’,1:」two」,length:2}複製代碼
5) 函數節流
當一個函數被頻繁調用時,若是會形成很大的性能問題的時候,這個時候能夠考慮函數節流,下降函數被調用的頻率
var throttle = function (fn, interval) { var __self = fn, timer, firstTime = true; return function () { var args = arguments, __me = this; if (firstTime) { // 若是是第一次調用,不需延遲執行 __self.apply(__me, args); return firstTime = false; } if (timer) { // 若是定時器還在,說明前一次延遲執行尚未完成 return false; } timer = setTimeout(function () { // 延遲一段時間執行 clearTimeout(timer); timer = null; __self.apply(__me, args); }, interval || 500); };};window.onresize = throttle(function () { console.log(1);}, 500);複製代碼
6) 惰性加載函數
在Web開發中,由於瀏覽器之間的實現差別,一些嗅探工做老是不可避免。好比咱們須要一個在各個瀏覽器中可以通用的事件綁定函數addEvent,常見的寫法以下:
var addEvent = function (elem, type, handler) { if (window.addEventListener) { return elem.addEventListener(type, handler, false); } if (window.attachEvent) { return elem.attachEvent('on' + type, handler); }};複製代碼
缺點:當它每次被調用的時候都會執行裏面的if條件分支,雖然執行這些if分支的開銷不算大,但也許有一些方法可讓程序避免這些重複的執行過程。
var addEvent = function (elem, type, handler) { if (window.addEventListener) { addEvent = function (elem, type, handler) { elem.addEventListener(type, handler, false); } } else if (window.attachEvent) { addEvent = function (elem, type, handler) { elem.attachEvent('on' + type, handler); } } addEvent(elem, type, handler);};複製代碼
此時addEvent依然被聲明爲一個普通函數,在函數裏依然有一些分支判斷。可是在第一次進入條件分支以後,在函數內部會重寫這個函數,重寫以後的函數就是咱們指望的addEvent函數,在下一次進入addEvent函數的時候,addEvent函數裏再也不存在條件分支語句。
Vue的源碼中(其餘優秀開源庫)也大量運用上述技巧。
六.ES6的類和繼承與ES5的區別
ES6的類
1. 關鍵字class
class Parent { constructor() { }}var p = new Parent();複製代碼
2. Babel => ES5
"use strict";function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function") }}var Parent = function Parent() { _classCallCheck(this, Parent)};var p = new Parent();複製代碼
ES5繼承
function Supertype(name) { this.name = name; this.colors = ["red", "green", "blue"];}Supertype.prototype.sayName = function () { console.log(this.name);};
function Subtype(name, age) { //繼承屬性 Supertype.call(this, name); this.age = age;} //繼承方法Subtype.prototype = new Supertype();Subtype.prototype.constructor = Subtype;Subtype.prototype.sayAge = function () { console.log(this.age);};
var instance1 = new Subtype('Annika', 21);instance1.colors.push("black"); console.log(instance1.colors); //["red", "green", "blue", "black"]instance1.sayName(); //Annikainstance1.sayAge(); //21
var instance2 = new Subtype('Anna',22);console.log(instance2.colors);//["red", "green", "blue"]instance2.sayName(); //Annainstance2.sayAge(); //22複製代碼
ES6的繼承
1.關鍵字extends
class Parent { constructor() { } p() {}}class Son extends Parent { constructor() { super(); } s() {}}var p = new Son();複製代碼
2. Babel => ES5
"use strict";var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; _again = false; if (object === null) { object = Function.prototype } var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; desc = parent = undefined; continue _function } } else { if ("value" in desc) { return desc.value } else { var getter = desc.get; if (getter === undefined) { return undefined } return getter.call(receiver) } } }};var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) { descriptor.writable = true } Object.defineProperty(target, descriptor.key, descriptor) } } return function (Constructor, protoProps, staticProps) { if (protoProps) { defineProperties(Constructor.prototype, protoProps) } if (staticProps) { defineProperties(Constructor, staticProps) } return Constructor }})();function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass) } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) { Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass }}function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function") }}var Parent = (function () { function Parent() { _classCallCheck(this, Parent) } _createClass(Parent, [{ key: "p", value: function p() {} }]); return Parent})();var Son = (function (_Parent) { _inherits(Son, _Parent); function Son() { _classCallCheck(this, Son); _get(Object.getPrototypeOf(Son.prototype), "constructor", this).call(this) } _createClass(Son, [{ key: "s", value: function s() {} }]); return Son})(Parent);var p = new Son();複製代碼
ES6的繼承區別和注意事項(注意了不少大廠面試都會問到的)
1.區別
ES6中 子類的__proto__ === 父類
ES5中 子類的__proto__ === Function.prototype
ES5中 子類不會繼承父類的靜態方法,ES6會
2. 注意事項
ES6 子類使用繼承後,必須如今constructor調用super,才能使用this,和實例化子類
這一篇內容比較多,謝謝你們可以看完,但願可以給你們可以喜歡。
最後,你們關注個人公衆號哦。
歡迎你們轉發,盡力一週一篇高質量原創文章。