我就不信這些js知識你都知道!<一>



第一篇分爲6個部分javascript

  1. this的指向問php

  2. 閉包java

  3. call、apply、bind詳解es6

  4. prototype、__proto__和constructor的關係面試

  5. 高階函數chrome

  6. 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));  };}複製代碼


用法

  1. 提升適用性


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,和實例化子類


這一篇內容比較多,謝謝你們可以看完,但願可以給你們可以喜歡。



最後,你們關注個人公衆號哦。

歡迎你們轉發,盡力一週一篇高質量原創文章。


相關文章
相關標籤/搜索