JS之函數(1)

圖片描述

前言

這段時間忽然發現JS原生好多東西都忘記了,但有些東西確實很重要,因此又從新再梳理一次。主要有函數的3種定義方法,ES5函數this指向,call與appl用法,JS常見的4種設計模式,原型鏈,原型鏈和繼承的方式(ES5和ES6)

1.函數的3種定義方法

1.1 函數聲明

//ES5
function getSum(){}
function (){}//匿名函數
//ES6
()=>{}//若是{}內容只有一行{}和return關鍵字可省,

1.2 函數表達式(函數字面量)

//ES5
var sum=function(){}
//ES6
let sum=()=>{}//若是{}內容只有一行{}和return關鍵字可省,

1.3 構造函數

var sum=new GetSum(num1,num2)

1.4 三種方法的對比

1.函數聲明有預解析,並且函數聲明的優先級高於變量;
2.使用Function構造函數定義函數的方式是一個函數表達式,這種方式會致使解析兩次代碼,影響性能。第一次解析常規的JavaScript代碼,第二次解析傳入構造函數的字符串html

2.ES5中函數的4種調用

在ES5中函數內容的this指向和調用方法有關node

2.1 函數調用模式

包括函數名()和匿名函數調用,this指向windowes6

function getSum() {
    console.log(this) //window
 }
 getSum()
 
 (function() {
    console.log(this) //window
 })()
 
 var getSum=function() {
    console.log(this) //window
 }
 getSum()

2.2 方法調用

對象.方法名(),this指向對象設計模式

var objList = {
   name: 'methods',
   getSum: function() {
     console.log(this) //objList對象
   }
}
objList.getSum()

2.3 構造器調用

new 構造函數名(),this指向構造函數數組

function Person() {
  console.log(this); //指向構造函數Person
}
var personOne = new Person();

2.4 間接調用

利用call和apply來實現,this就是call和apply對應的第一個參數,若是不傳值或者第一個值爲null,undefined時this指向windowapp

function foo() {
   console.log(this);
}
foo.apply('我是apply改變的this值');//我是apply改變的this值
foo.call('我是call改變的this值');//我是call改變的this值

3.ES6中函數的調用

箭頭函數不能夠看成構造函數使用,也就是不能用new命令實例化一個對象,不然會拋出一個錯誤
箭頭函數的this是和定義時有關和調用無關
調用就是函數調用模式dom

(() => {
   console.log(this)//window
})()

let arrowFun = () => {
  console.log(this)//window
}
arrowFun()

let arrowObj = {
  arrFun: function() {
   (() => {
     console.log(this)//arrowObj
   })()
   }
 }
 arrowObj.arrFun();

4.call,apply和bind

1.IE5以前不支持call和apply,bind是ES5出來的;
2.call和apply能夠調用函數,改變this,實現繼承和借用別的對象的方法;函數

4.1 call和apply定義

調用方法,用一個對象替換掉另外一個對象(this)
對象.call(新this對象,實參1,實參2,實參3.....)
對象.apply(新this對象,[實參1,實參2,實參3.....])性能

4.2 call和apply用法

1.間接調用函數,改變做用域的this值
2.劫持其餘對象的方法this

var foo = {
  name:"張三",
  logName:function(){
    console.log(this.name);
  }
}
var bar={
  name:"李四"
};
foo.logName.call(bar);//李四
實質是call改變了foo的this指向爲bar,並調用該函數

3.兩個函數實現繼承

function Animal(name){   
  this.name = name;   
  this.showName = function(){   
    console.log(this.name);   
  }   
}   
function Cat(name){  
  Animal.call(this, name);  
}    
var cat = new Cat("Black Cat");   
cat.showName(); //Black Cat

4.爲類數組(arguments和nodeList)添加數組方法push,pop

(function(){
  Array.prototype.push.call(arguments,'王五');
  console.log(arguments);//['張三','李四','王五']
})('張三','李四')

5.合併數組

let arr1=[1,2,3]; 
let arr2=[4,5,6]; 
Array.prototype.push.apply(arr1,arr2); //將arr2合併到了arr1中

6.求數組最大值

Math.max.apply(null,arr)

7.判斷字符類型

Object.prototype.toString.call({})

4.3 bind

bind是function的一個函數擴展方法,bind之後代碼從新綁定了func內部的this指向,不會調用方法,不兼容IE8

var name = '李四'
 var foo = {
   name: "張三",
   logName: function(age) {
   console.log(this.name, age);
   }
 }
 var fooNew = foo.logName;
 var fooNewBind = foo.logName.bind(foo);
 fooNew(10)//李四,10
 fooNewBind(11)//張三,11  由於bind改變了fooNewBind裏面的this指向

4.4 call,apply和bind原生實現

call實現:

Function.prototype.newCall = function(context, ...parameter) {
  context.fn = this;  
  context.fn(...parameter);
  delete context.fn;
}
let person = {
  name: 'Abiel'
}
function sayHi(age,sex) {
  console.log(this.name, age, sex);
}
sayHi.newCall (person, 25, '男'); // Abiel 25 男

apply實現:

Function.prototype.newApply = function(context, parameter) {
  if (typeof context === 'object') {
    context = context || window
  } else {
    context = Object.create(null)
  }
  let fn = Symbol()
  context[fn] = this
  context[fn](parameter);
  delete context[fn]
}

bind實現:

Function.prototype.bind = function (context,...innerArgs) {
  var me = this
  return function (...finnalyArgs) {
    return me.call(context,...innerArgs,...finnalyArgs)
  }
}
let person = {
  name: 'Abiel'
}
function sayHi(age,sex) {
  console.log(this.name, age, sex);
}
let personSayHi = sayHi.bind(person, 25)
personSayHi('男')

4.5 三者異同

同:都是改變this指向,均可接收參數
異:bind和call是接收單個參數,apply是接收數組

5.函數的節流和防抖

類型 概念 應用
節流 某個時間段內,只執行一次 滾動條,resize事件一段時間觸發一次
防抖 處理函數截止後一段時間依次執行 scroll,resize事件觸發完後一段時間觸發

節流:

5.1 節流

let throttle = function(func, delay) {
    let timer = null;
    return function() {
      if (!timer) {
        timer = setTimeout(function() {
          func.apply(this, arguments);
          timer = null;
        }, delay);
      }
    };
  };
  function handle() {
    console.log(Math.random());
  }
  window.addEventListener("scroll", throttle(handle, 1000)); //事件處理函數

5.2 防抖

function debounce(fn, wait) {
    var timeout = null;
    return function() {
      if (timeout !== null) clearTimeout(timeout);//若是屢次觸發將上次記錄延遲清除掉
      timeout = setTimeout(function() {
          fn.apply(this, arguments);
          timer = null;
        }, wait);
    };
  }
  // 處理函數
  function handle() {
    console.log(Math.random());
  }
  // 滾動事件
  window.addEventListener("onscroll", debounce(handle, 1000));

6.原型鏈

6.1 定義

對象繼承屬性的一個鏈條

6.2構造函數,實例與原型對象的關係

圖片描述

var Person = function (name) { this.name = name; }//person是構造函數
var o3personTwo = new Person('personTwo')//personTwo是實例

圖片描述

原型對象都有一個默認的constructor屬性指向構造函數

6.3 建立實例的方法

1.字面量

let obj={'name':'張三'}

2.Object構造函數建立

let Obj=new Object()
Obj.name='張三'

3.使用工廠模式建立對象

function createPerson(name){
 var o = new Object();
 o.name = name;
 };
 return o; 
}
var person1 = createPerson('張三');

4.使用構造函數建立對象

function Person(name){
 this.name = name;
}
var person1 = new Person('張三');

6.4 new運算符

1.創了一個新對象;
2.this指向構造函數;
3.構造函數有返回,會替換new出來的對象,若是沒有就是new出來的對象
4.手動封裝一個new運算符

var new2 = function (func) {
    var o = Object.create(func.prototype);    //建立對象
    var k = func.call(o);             //改變this指向,把結果付給k
    if (typeof k === 'object') {         //判斷k的類型是否是對象
        return k;                  //是,返回k
    } else {
        return o;                  //不是返回返回構造函數的執行結果
    }
}

更多詳情:詳談JavaScript原型鏈

6.5 對象的原型鏈

圖片描述

7.繼承的方式

JS是一門弱類型動態語言,封裝和繼承是他的兩大特性

7.1原型鏈繼承

將父類的實例做爲子類的原型
1.代碼實現
定義父類:

// 定義一個動物類
function Animal (name) {
  // 屬性
  this.name = name || 'Animal';
  // 實例方法
  this.sleep = function(){
    console.log(this.name + '正在睡覺!');
  }
}
// 原型方法
Animal.prototype.eat = function(food) {
  console.log(this.name + '正在吃:' + food);
};

子類:

function Cat(){ 
}
Cat.prototype = new Animal();
Cat.prototype.name = 'cat';

// Test Code
var cat = new Cat();
console.log(cat.name);//cat
console.log(cat.eat('fish'));//cat正在吃:fish  undefined
console.log(cat.sleep());//cat正在睡覺! undefined
console.log(cat instanceof Animal); //true 
console.log(cat instanceof Cat); //true

2.優缺點
簡單易於實現,可是要想爲子類新增屬性和方法,必需要在new Animal()這樣的語句以後執行,沒法實現多繼承

7.2 構造繼承

實質是利用call來改變Cat中的this指向
1.代碼實現
子類:

function Cat(name){
  Animal.call(this);
  this.name = name || 'Tom';
}

2.優缺點
能夠實現多繼承,不能繼承原型屬性/方法

7.3 實例繼承

爲父類實例添加新特性,做爲子類實例返回
1.代碼實現
子類

function Cat(name){
  var instance = new Animal();
  instance.name = name || 'Tom';
  return instance;
}

2.優缺點
不限制調用方式,但不能實現多繼承

7.4 拷貝繼承

將父類的屬性和方法拷貝一份到子類中
1.子類:

function Cat(name){
  var animal = new Animal();
  for(var p in animal){
    Cat.prototype[p] = animal[p];
  }
  Cat.prototype.name = name || 'Tom';
}

2.優缺點
支持多繼承,可是效率低佔用內存

7.5 組合繼承

經過調用父類構造,繼承父類的屬性並保留傳參的優勢,而後經過將父類實例做爲子類原型,實現函數複用
1.子類:

function Cat(name){
  Animal.call(this);
  this.name = name || 'Tom';
}
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;

7.6 寄生組合繼承

function Cat(name){
  Animal.call(this);
  this.name = name || 'Tom';
}
(function(){
  // 建立一個沒有實例方法的類
  var Super = function(){};
  Super.prototype = Animal.prototype;
  //將實例做爲子類的原型
  Cat.prototype = new Super();
})();

7.7 ES6的extends繼承

ES6 的繼承機制是先創造父類的實例對象this(因此必須先調用super方法),而後再用子類的構造函數修改this,連接描述

//父類
class Person {
    //constructor是構造方法
    constructor(skin, language) {
        this.skin = skin;
        this.language = language;
    }
    say() {
        console.log('我是父類')
    }
}

//子類
class Chinese extends Person {
    constructor(skin, language, positon) {
        //console.log(this);//報錯
        super(skin, language);
        //super();至關於父類的構造函數
        //console.log(this);調用super後獲得了this,不報錯,this指向子類,至關於調用了父類.prototype.constructor.call(this)
        this.positon = positon;
    }
    aboutMe() {
        console.log(`${this.skin} ${this.language}  ${this.positon}`);
    }
}

//調用只能經過new的方法獲得實例,再調用裏面的方法
let obj = new Chinese('紅色', '中文', '香港');
obj.aboutMe();
obj.say();

更多詳情請戳:JS繼承的實現方式

8.高階函數

8.1定義

函數的參數是函數或返回函數

8.2 常見的高階函數

map,reduce,filter,sort

8.3 柯里化

1.定義:只傳遞給函數一部分參數來調用它,讓它返回一個函數去處理剩下的參數

fn(a,b,c,d)=>fn(a)(b)(c)(d)

2.代碼實現:

let currying = function(fn) {
    // args 獲取第一個方法內的所有參數
    var args = Array.prototype.slice.call(arguments, 1)
    return function() {
        // 將後面方法裏的所有參數和args進行合併
        var newArgs = args.concat(Array.prototype.slice.call(arguments))
        // 把合併後的參數經過apply做爲fn的參數並執行
        return fn.apply(this, newArgs)
    }
}

8.4 反柯里化

1.定義:

obj.func(arg1, arg2)=>func(obj, arg1, arg2)

2.代碼實現:

Function.prototype.uncurrying = function() {
  var that = this;
  return function() {
    return Function.prototype.call.apply(that, arguments);
  }
};
 
function sayHi () {
  return "Hello " + this.value +" "+[].slice.call(arguments);
}
let sayHiuncurrying=sayHi.uncurrying();
console.log(sayHiuncurrying({value:'world'},"hahaha"));

8.5偏函數

1.定義:指定部分參數來返回一個新的定製函數的形式
2.例子:

function foo(a, b, c) {
  return a + b + c;
}
function func(a, b) {
  return foo(a,b,8);
}

參考文獻:

https://www.cnblogs.com/tugen...
https://www.cnblogs.com/humin...
https://www.cnblogs.com/cheng...
https://www.cnblogs.com/cheng...

相關文章
相關標籤/搜索