JavaScript函数

目录

函数基础

函数声明

写法

声明提升

函数内部属性

arguments

rest parameters

length属性

callee 属性

函数作用域

概述

作用

作用域链

函数this指向

全局调用

对象方法调用

事件调用

改变函数this指向

1.call方法

2.apply方法

 3.bind方法

bind使用场景举例

立即执行函数 IIFE

写法

特点

举例

回调函数

同步回调函数

异步回调函数

闭包函数

垃圾回收机制

内存泄漏

闭包

构造函数

概述

对象的创建

构造函数和实例对象

原型和原型链

原型prototype

隐式原型__proto__

原型链

补充:

总结

继承

原型链继承

extend关键字

 箭头函数

概述

 箭头函数特点

补充:


函数基础

在JavaScrip中,引用数据类型只有Object;而函数是一种特殊的对象。

函数声明

写法

function 函数名(形参列表){
    //函数体
}
//函数表达式
var 函数名 = function(形参列表){
    //函数体
}

声明提升

  • 函数声明与var声明的变量类似,也存在声明提升,即在声明之前也可以使用
fn1();  //输出:"具名函数",fn1
function fn1(){
    //  函数声明,声明提升
    console.log("具名函数",fn1.name);
}

fn2()    //报错,  const不具备变量提升,fn2声明赋值前不能调用
const fn2 = function(){
    // 函数表达式。(是赋值语句的一部分),将匿名函数赋值给fn2
    console.log("匿名函数");
}

函数内部属性

  • 函数内部属性只能在函数内部才能访问

arguments

  1. arguments(动态参数)是一类数组对象(伪数组),除了length和索引外,不能用任何数组的方法
  2. 它包含传递给函数的参数,是所有(非箭头)函数中都可用的局部变量
  3.  它并不总是与函数参数列表保持同步例如,如果你更改了 arguments 对象的某个值,它可能不会更改相应的函数参数值。

function fun(){
  console.log(arguments) // [Arguments] { '0': 1, '1': 2, '2': 3, '3': 4 }
  console.log(arguments[1]) // 2

  let sum = 0
  for (let i = 0; i < arguments.length; i++) {
    sum += arguments[i]
  }
  console.log(sum) //10
}
// 当传递的实参个数超过形参的个数的时候不会报错,所有的实参都会保存在arguments里
fun(1,2,3,4) 

注意:arguments 中存的是实参,而不会存形参

function fun(a,b = 2,c = 3){
  console.log(arguments) // [Arguments] { '0': 1 }
  console.log(b) //2
  console.log(c) //3
}
//只传了一个实参,那么arguments中就只有一个值
fun(1) 

rest parameters

  • rest parameters(剩余参数),拥有更清晰的语法,并且返回的是一个真正的数组,这意味着可以使用所有数组方法(如push、pop、map、filter等)来操作它
  • 箭头函数也可以使用(后面介绍)
const fun = (first, second, ...rest) => {
    console.log(first, second) // 输出: 1 2
    rest.unshift(666)
    console.log(rest) // 输出:[ 666,3, 4, 5]
}
fun(1, 2, 3, 4, 5)

length属性

  • 函数的length属性,返回形参个数

  • arguments的length属性,返回实参个数

function fun(num1, num2, num3) {
    console.log(arguments.length) //5
    console.log(fun.length) //3
}
fun(1, 2, 3, 4, 5)

callee 属性

  • arguments 对象有一个名为callee的属性,该属性是一个指针,指向拥有这个arguments 对象的函数,实际上就是函数名。
// 递归求n的阶乘
function testFun(n) {
    if (n === 1) return 1
    return arguments.callee(n - 1) * n //arguments.callee 相当于函数名testFun
}
console.log(testFun(3)) //6

函数作用域

概述

  • 作用域:是程序中变量、函数等标识符的可见性(即在哪里可以访问这些标识符)
  • 全局作用域:在当前脚本的任意位置都可以访问
  • 函数作用域:每个函数在执行时都会创建一个新的作用域
  • 局部变量: 函数内部声明的变量,仅在该函数内部可见和可访问(除非这些变量被明确暴露到函数外部,比如return)
  • 闭包: 函数可以访问并操作函数外部的变量(即父级作用域中的变量)。当函数发生嵌套时,“内部函数” 与 “内部函数调用的外部函数的变量” 就组成了闭包

作用

作用域最大的用处就是隔离变量,不同作用域下同名变量不会有冲突

作用域链

  • 作用域链是由多个作用域对象(Scope Objects)组成的链表,它决定了在代码执行过程中如何查找变量和函数的定义
  • 由于函数可以访问父级作用域中的变量,故一层层嵌套的函数创建的作用域,形成了一条作用域链
  • 变量访问时,遵循就近原则,从自身开始沿作用域链向上查找
var globalVar = 'Global'
function outerFunction() {
    var outerVar = 'Outer'
    function innerFunction() {
        var innerVar = 'Inner'
        console.log(innerVar) // 访问当前作用域,找到输出 Inner
        console.log(outerVar) // 访问当前作用域未找到,访问上级作用域找到,输出 Outer
        console.log(globalVar) // 依次访问作用域链,直至全局作用域,找到,输出 Global
        // console.log(nonExistentVar) //依次访问作用域链,均未找到
    }
    innerFunction()
}
outerFunction()

函数this指向

  • this是执行环境上下文对象,它指向谁取决于什么时候调用、被谁调用。简单来说就是,“谁调用这个函数,this就指向谁”
  • 在显式函数绑定时,我们可以自己决定this的指向
  • 以下结果为均为浏览器中的结果,不包括node环境中下

全局调用

  • 指向全局对象,浏览器中的全局对象就是window
function fun1() {
    console.log(this)
}
// window.fun1()
fun1() //window

console.log(this)   //window

对象方法调用

  • 指向调用对象
const person = {
    name: 'person',
    testFun: function () {
        console.log(this)
    }
}
person.testFun() // {name: 'person', testFun: ƒ}
const windowUse = person.testFun
windowUse() //window

事件调用

<button class="btu1">btu1</button>
document.querySelector('.btu1').addEventListener('click', function () {
        console.log(this) //指向(button标签)DOM对象
})

改变函数this指向

一般情况,函数的this指向其调用者,但某些时候,我们可能需要改变函数的this指向,让它指向我们所期望的对象,Function.prototype.apply()/.call()/.bind()

1.call方法

fun.call(thisArg,arg1,arg2....)传参arg1...   thisArg就是函数运行时指定的this值

const person = {
    name: 'Alice'
}
function greet(x, y) {
    console.log(this.name + `, hello!,today is ${x + y}th`)
}
greet.call(person, 5, 6) // Alice, hello!,today is 11th
2.apply方法

fun.apply(thisArg,[argArray])    传的值得包含在数组里(apply主要与数组相关)

使用apply会比call更方便传不定数量的实参

const person = {
    name: 'Alice',
    array: [1, 2, 3, 4, 5]
}
function greet(...rest) {
    const today = Math.max(...rest)
    console.log(this.name + `, hello!,today is ${today}th`)
}
greet.apply(person, person.array) // Alice, hello! today is 5th
 3.bind方法

fun.bind(thisArg,arg1,arg2....),与call类似

bind与apply和call一样,也能改变this指向,但不同的是,bind在改变this指向时不会调用函数

const person = {
    name: 'Alice',
    greet: function (greeting) {
        console.log(`${greeting}, ${this.name}!`)
    }
}
// 使用bind预设参数'Hello'
const greetHello = person.greet.bind(person, 'Hello')
greetHello() // 输出: Hello, Alice!
// 也可以不预设参数,仅改变this指向
const greet = person.greet.bind(person)
greet('Hi') // 输出: Hi, Alice!
bind使用场景举例

一个button点击后禁用2S,后恢复

<button class="btu1">点击</button>

const btu = document.querySelector('.btu1')
btu.addEventListener('click', function () {
    // 禁用按钮
    this.disabled = true
    console.log('禁用')
    window.setTimeout(
        function () {
            // 将指向window的this指向btu
            this.disabled = false
            console.log('启用')
        }.bind(this),
        2000
    ) //这样设置,改动btu不会造成影响
    // 在此处,必须将fun.bind(btu)设置为setTimeout的回调函数参数
        //,但外层函数的this指向btu
    // 故等价于下面的写法
    setTimeout(() => {
        this.disabled = true
        console.log('再次禁用')
    }, 4000)
})

箭头函数相关问题若不了解可暂放置,先接着阅读下文

总结:call和apply都是改变上下文中的this并立即执行这个函数,bind方法可以让对应的函数想什么时候调就什么时候调用,并且可以将参数在执行的时候添加。

立即执行函数 IIFE

  • Immediately Invoked Function Expression,意为立即调用的函数表达式,也就是说,声明函数的同时立即调用这个函数
  • 也叫匿名函数的自运行,因为一般声明的是匿名函数

写法

(function() {  
    // 代码块  
    console.log('这是一个立即执行函数');  
})();

注意:如果我们写了普通函数,又写了立即执行函数,要注意加分号隔开,否则 js 编译器会把它们解析成一条语句,就会报错:

// 普通函数
function fun() {
    console.log(123)
}
// 如果后面跟的是立即执行函数,前面的结束语句必须要加分号
// 立即执行函数
;(function (a) {
    console.log(123 + a)
})(123)

特点

  1. 它是一个匿名函数,被包含在圆括号()
  2. 立即执行,函数定义后面跟着一对圆括号(),这对圆括号的作用是调用前面定义的函数
  3. 页面加载完成后只执行一次的设置函数
  4. 创建一个独立的作用域,使得函数内部定义的变量不会影响到全局作用域

举例

for (let i = 0; i < 6; i++) {
    function fun() {
        console.log(i)
    }
}
fun()   //输出5
for (let i = 0; i < 6; i++) {
    ;(function fun() {
        console.log(i)
    })()//依次输出1 2 3 4 5
}

更多立即执行函数内容,推荐参考深入理解JavaScript——立即执行函数(IIFE) - 个人文章 - SegmentFault 思否

回调函数

在JavaScript中,允许你将一个函数作为参数传递给另一个函数,并在某个特定操作完成或某个条件满足时执行。这种机制增加了代码的灵活性和可重用性。

同步回调函数

虽然回调函数更常用于异步操作,但也可以用于同步操作

function doSomething(callback) {  
    console.log('Doing something...');  
    // 假设这里有一些逻辑处理  
    callback(); // 当我们完成某些工作时,调用回调函数  
}  
  
function callbackFunction() {  
    console.log('Callback function called!');  
}  
  
doSomething(callbackFunction);  
// 输出:  
// Doing something...  
// Callback function called!

异步回调函数

在异步操作中,回调函数尤为重要,因为它允许我们在某个异步操作(如网络请求、文件读取等)完成后执行代码。

function fetchData(url, callback) {  
    // 假设这里是一个异步操作,比如使用XMLHttpRequest或fetch API获取数据  
    console.log('Fetching data from ' + url);  
    // 模拟异步操作完成  
    setTimeout(() => {  
        // 假设这是从url获取的数据  
        const data = 'Some data from ' + url;  
        callback(data); // 调用回调函数,并传入数据  
    }, 1000); // 假设异步操作需要1秒  
}  
  
function displayData(data) {  
    console.log('Displaying data:', data);  
}  
  
fetchData('https://example.com/data', displayData);  
// 输出:  
// Fetching data from https://example.com/data  
// (等待1秒后)  
// Displaying data: Some data from https://example.com/data

注意:虽然回调函数非常有用,但它也带来了一些问题,如“回调地狱”(Callback Hell),特别是在处理多个嵌套的异步操作时。为了解决这个问题,JS引入了Promises,async/await等更现代的异步编程模式。

闭包函数

垃圾回收机制

垃圾回收机制是自动检测和释放不再被使用的内存空间,以便系统可以重新利用该空间。它通过识别不再被程序所引用的数据,并将其标记为垃圾,然后释放相关的内存空间。

内存泄漏

1. 垃圾回收器没办法回收所有的不需要内存,只能回收明确不需要的内存(不再被引用的内存)

2. 对于不需要的,但垃圾回收器没有回收的内存部分,称作“内存泄漏”

3. 通常是由于对象或数据结构在不再需要时仍被引用,导致垃圾回收器无法释放其占用的内存

简单来说就是垃圾回收器不够智能,无法准确理解需要回收哪些内存。为确保不会回收程序还会用到的内存,做出让步,只回收明确不再被引用的数据的那部分内存,这就导致了可能会有一部分无用却未能回收的内存,这部分就叫做内存泄露。但一般来说垃圾回收器能回收99%的垃圾,所以内存泄露部分的影响不会很大。

内存泄漏举例

function createLeakyElement() {
    var element = document.createElement('div')
    element.addEventListener('click', function () {
        // ...
    })
    // 如果没有其他地方引用element,并且没有从DOM中移除它,
    // 这个匿名函数和element都会一直存在,导致内存泄漏
    return element
}

闭包

闭包的一个重要作用就是,让变量不会被垃圾回收器回收。

 =>          虽然全局变量不会被垃圾回收器回收,但在多人协作开发时,频繁使用全局变量很容易会造成全局污染(比如:A创建的变量被B覆盖或被误改)     

// A创建count变量
var count = 0
// B声明覆盖了count变量
var count = 2

 =>          函数可以创建私有作用域的特性便完美的解决了这个问题。将变量放入函数,可使数据私有化,除非明确地将内部变量暴露出去,否则外部便无法直接修改内部数据。但是,一般的函数在被调用完后,便不会被引用,也就会被垃圾回收器给回收内存。

function fun() {
    let count = 1
    count++
    console.log(count)
}
//执行完后,立即释放函数所占内存
for (let i = 5; i != 0; i--) {
    fun() //输出 2 2 2 2 2
}

console.dir(fun)//[[Scopes]]展示的函数作用域链中,没有count和fun

//console.log(count) //报错, 无法直接访问,无法直接修改

 =>          为了解决这个问题,便有了闭包。因为函数可以访问并操作函数外部的变量(即父级作用域中的变量)。所以如果在函数内部嵌套一层函数,并在内层函数内引用外层函数的变量,就可以避开垃圾会收器的回收机制。这也是闭包函数的核心作用        =>        使变量可以驻留在内存,不被回收(Vue的组件便是利用了闭包函数的特性)

function fun() {
    let count = 1
    return function () {
        count++
        console.log(count)
    }
}
//执行完后,不会释放内存
let testFun = fun()
for (let i = 5; i != 0; i--) {
    testFun() //输出 2 3 4 5 6
}

//[[Scopes]]展示的函数作用域链中,(fun){ count:6 } 存在,未被回收
console.dir(testFun) 

注意:如果后续不再使用fun2,但fun2不会被自动回收,将造成内存泄漏

由于testFun是匿名函数,由fun中返回的函数匿名。fun2只是一个变量名所以手动回收只需要

testFun = null // 手动解除对闭包的引用
console.dir(fun)

可以看到全局变量testFun的值为null,且作用域链中的 (fun){ count:6 } 已被回收

但需要注意的是,声明testFun时尽量不要用const,因为const声明的变量无法修改地址,故不能重新为testFun赋值为null,而在全局声明的变量又不会被回收,所以使用const声明有可能会无法手动回收内存。

注意:手动解除引用后,该内存会成为垃圾回收器的回收目标,但又能可不会立即回收,其回收时间由垃圾回收器决定

闭包的作用实例:

let makeCounter = function () {
    let count = 0
    function changeBy(val) {
        count += val
    }
    // 给什么,才能拿什么
    return {
        add: function () {
            changeBy(1)
        },
        reduce: function () {
            changeBy(-1)
        },
        getGalue: function () {
            return count
        }
    }
}
let counter1 = makeCounter()
let counter2 = makeCounter()
for (let i = 3; i != 0; i--) {
    counter1.add()
    counter2.reduce()
}
console.log(counter1.getGalue()) //3
console.log(counter2.getGalue()) //-3
// counter1 ,counter2拥有各自独立的词法作用域
// 面向对象编程----数据的隐藏和封装

总结:闭包就是   :  1.函数内部嵌套一个函数         2.内层函数要用到层部函数的变量

          闭包的作用:  1.避免全局污染         2.创建私有作用域,外部无法修改内部数据(vue组件)

                               3.可以让外部可以使用内部变量(get,return)

                               4.(核心作用)使变量可以驻留在内存,不被回收

构造函数

概述

  • 在JavaScript中  "函数除了被当作函数还可以当作class"
  • 构造函数用于创建具有特定属性和方法的对象,它是JS实现面向对象的主要语法,类似于Java中的类与继承(并且,在ES6中已经支持class关键字)

对象的创建

//第一种:字面量
let user1 = {
    name: 'Jack',
    age: 18,
    login: function () {
        //......
        console.log('用户登录')
    }
}


//第二种: Object构造函数模式
var user1 = new Object()
user1.name = 'Jack'
user1.age = 18
user1.login = function () {
    console.log('用户登录')
}


//第三种: 自定义构造函数
function User(name, age) {
    this.name = name
    this.age = age
}
User.prototype.login = function () {
    console.log('用户登录')
}
let user1 = new User('Jack', 18)
user1.login()


//第四种:使用clss关键字
class User {
    constructor(name, age) {
        this.name = name
        this.age = age
    }
    login() {
        //......
        console.log('用户登录')
    }
}
let user1 = new User('Jack', 18)
user1.login()

 在上面的例子中,User()就是构造函数,而user1是User的实例对象;

虽然字面量创建是我们最常用的对象创建方式,但仔细观察就能会发现:

当我们需要多个user的时候,字面量方式我们需要重复代码去创建对象;而使用构造函数的方式只需要写一遍属性和方法,我们就可以通过new关键字,new出多个“相似而又不同的对象”

构造函数和实例对象

通过构造函数可以创建实例对象,而每个实例对象(无论是通过字面量、构造函数还是其他方式创建的)都可以访问一个构造函数属性(constructor),它指向实例的构造函数,通过构造函数创建的对象拥有构造函数内部定义的属性和方法。

function User(name) {
    this.name = name
}

let user1 = new User('Jack')
let user2 = new User('Anna')

console.log(user1.constructor === User) //true
console.log(user2.constructor === User) //true


let user3 = {}
let user4 = new Object()

console.log(user3.constructor === Object) //true
console.log(user4.constructor === Object) //true

在JS所以引用数据类型都是Object,那么函数作为一种特殊的对象,也应当有其构造函数,也就拥有其constructor

function User(name) {
    this.name = name
}
console.log(User.constructor === Function) //true
console.log(Function.constructor === Function) //true

所有函数(函数声明或箭头函数声明的函数)都是由Function创建,包括Function自己

原型和原型链

原型prototype

1.  在JS中,每个构造函数都有一个prototype属性,称之为原型,这个属性是一个对象(所以也可以叫做原型对象),它包含了可以由该构造函数的实例可访问的属性和方法

2.  所以原型是用来存放一些可以共享给实例对象用属性和方法的(减少内存浪费)。比如,上面提到的constructor属性,其实就存放在构造函数的prototype属性中,只不过每个实例对象都可以访问这个属性。

function User(name) {
    this.name = name
}
User.prototype.login = function () {
    console.log('登录')
}
const user1 = new User("流萤")

console.dir(User.prototype)

 查看构造函数User()的prototype对象

3.当你定义一个函数时,JavaScript会自动为这个函数添加一个名为prototype的特殊属性。这个prototype属性是一个对象,它默认包含一个constructor属性,该属性指向该函数本身。

4.可以向这个prototype对象上添加任何你想要的属性和方法。这些属性和方法将被所有通过new关键字和该函数创建的实例对象所共享。

注意:任何(非箭头)函数都有一个原型prototype属性,因为任何(非箭头)函数都可以作为构造函数

 构造函数和实例之间就初步构成了这样一个关系,如图:

隐式原型__proto__

 在JS中,每个对象都有一个__proto__属性(在ES6的class语法中,这个属性被[[Prototype]]内部槽位替代,但对外表现为prototype)指向更上一级的原型,也就是实例对象的构造函数的原型,比如new创建的新实例对象内部的 __proto__属性 会  指向构造函数的 prototype对象

function User(name) {
    this.uname = name
}
const user1= new User('流萤')
console.log(user1.__proto__ === User.prototype) //true

在这个例子中,实例对象user1的__proto__属性(或[[prototype]]内部槽位)指向了它的构造函数User()的原型对象User.prototype

原型链

1.  每个对象都有__proto__属性,并通过它链接到一个原型对象,而原型对象本身有它自己的__proto__属性(即它的原型),这样就形成了一个链式结构,称为原型链。所以__proto__的作用就是为原型链查找提供方向

function User(name) {
    this.uname = name
}
const user1 = new User('流萤')

console.log(user1.__proto__.__proto__ === Object.prototype) //true

在这个例子中的user1.__proto__指向了User.prototype,User.prototype而也有一个__proto__属性指向了Object.prototype 

2.   当查找一个对象的属性或方法时,JS会首先在当前对象上查找,如果找不到,则会沿着原型链向上查找,直到找到为止或到达原型链的末尾(即null)。比如数组的构造函数是Array(),所以数组都可以使用Array.prototype上的方法(push,pop等)

前面所提到的实例访问其构造函数上的属性(constructor)和方法就是通过__proto__(或[[prototype]]内部槽)来实现的

补充:

  •  在JS中,所有的对象,包括构造函数的原型对象,最终都继承自 Object.prototype,这是js原型链的顶点。
  • 原型对象Object.prototype作为原型链的顶端,位于原型链的最末端。因此,它不再有自己的原型,所以Object.prototype.__proto__ 指向null,表示原型链的终点。
console.log(Object.prototype.__proto__)//null
  • __proto__(或[[prototype]]内部槽)是对象持有的,prototype是函数持有的,但是在JS中函数作为特殊的对象也是拥有__proto__的,且其指向Function.prototype
function User() {}
console.log(User.__proto__ === Function.prototype) //true
  • Object(),Function(),Array(),Set()......这些内置对象,特殊对象呢,它们也是一种特殊的构造函数,具有__proto__属性。那么它们的__proto__属性又指向谁呢?
console.log(Function.__proto__ === Function.prototype)  //true
console.log(Object.__proto__ === Function.prototype)    //true
console.log(Array.__proto__ === Function.prototype)     //true
console.log(Set.__proto__ === Function.prototype)       //true

答案是Function.prototype。所以我们。“可以看作是Function创造了所有函数,包括它自己”

那么Function.prototype的__proto__属性又指向谁呢?

 console.log(Function.prototype.__proto__ === Object.prototype) //true

答案是JS原型链的顶点Object.prototype

所以最终的原型链关系图就变成了这样:

思考:Array,Set,Math等的prototype和__proto__在关系图中应该是怎么样的呢?

总结

  1. 每个对象均存在隐式原型(__proto__),“函数对象”才有prototype属性
  2. __proto__存在的意义在于为原型链查找提供方向,原型链查找靠的是__proto__,而不是prototype
  3. “函数对象”的__proto__都指向Function.prototype
  4. 每个对象都有一个隐式原型属性(__proto__),多个原型通过__proto__链接在一起形成的链式结构就是原型链

继承

原型链继承

这是JavaScript中最基本的继承方式。我们通过将子构造函数的prototype设置为一个父构造函数的实例来实现继承。这样,子构造函数的实例就可以访问到父构造函数实例的属性和方法(通过原型链)。

function User(name, sex) {
    this.name = name
    this.sex = sex
}
User.prototype.login = function () {
    console.log('用户 ' + this.name + '  登录')
}
function FemaleUser(name, age) {
    User.call(this, name, '女') // 借用构造函数继承属性
    this.age = age
}
// 设置FemaleUser的原型为User的一个实例,FemaleUser继承了User通过原型,形成链条
FemaleUser.prototype = Object.create(User.prototype)
//prototype.constructor指向构造函数
FemaleUser.prototype.constructor = FemaleUser
FemaleUser.prototype.printAge = function () {
    console.log('刚满' + this.age + '岁~')
}
const FemaleUser1 = new FemaleUser('流萤', 18)
FemaleUser1.login()//用户 流萤 登录
FemaleUser1.printAge()//刚满18岁~

extend关键字

在ES6及更高版本中,可以使用类(Class)来定义对象的行为和属性。类提供了一个更正式和清晰的方式来定义原型链,并减少了直接操作原型的需要。

class User {
    constructor(name, sex) {
        this.name = name
        this.sex = sex
    }
    login() {
        console.log('用户 ' + this.name + ' 登录')
    }
}
class FemaleUser extends User {
    constructor(name, age, sex) {
        // 调用父类的构造函数
        super(name, sex)
        // 定义子类特有的属性
        this.age = age
    }
    // 可以添加更多方法或覆盖父类方法
    printAge() {
        console.log('刚满' + this.age + '岁~')
    }
}
const FemaleUser1 = new FemaleUser('流萤', 18, '女') // 注意这里添加了'女'作为sex参数
FemaleUser1.login()//用户 流萤 登录
FemaleUser1.printAge()//刚满18岁~

 箭头函数

概述

  • 箭头函数主要用于非方法函数的定义,特别是那些只有一个表达式并且需要隐式返回值的函数
const sum = (a, b) => a + b;

 箭头函数特点

1. 不绑定自己的this:它们会捕获其所在(即定义时)的上下文的this值作为自己的this值。这意味着,在箭头函数内部使用this时,它总是指向函数被定义时所在的对象,而不是函数被调用时所在的对象。这是解决在回调函数中使用this指向问题的一个非常优雅的方案。

<button class="btu1">btu1</button>
<button class="btu2">btu2</button>

// 箭头函数内不存在this,它默认绑定外层this的值(延用上一层的)
// 箭头函数中this引用的就是最近作用域的的this(一层层查找)
document.querySelector('.btu1').addEventListener('click', function(){
    console.log(this) //指向 <button class="btu1">btu1</button>
})
document.querySelector('.btu2').addEventListener('click', () => {
    console.log(this) //指向window
})

箭头函数解决在回调函数中使用this指向问题案例:

class Counter {
    constructor() {
        this.count = 0
        // 假设我们想在某个异步操作完成后更新count
        setTimeout(this.increment, 1000) // 这里会有问题
        // 使用箭头函数修复
        setTimeout(() => this.increment(), 1000)
    }
    increment() {
        this.count++
        console.log(this.count)
    }
}
const counter = new Counter()

在这个例子中,Counter类有一个count属性和一个increment方法来增加count的值。构造函数中,我们尝试使用setTimeout来延迟调用increment方法。如果我们直接使用this.increment作为setTimeout的回调,由于setTimeout的回调函数的this指向全局对象(在非严格模式下)或undefined(在严格模式下),this.increment()将不会按预期工作,因为它将尝试在全局作用域下访问count属性,而该属性并不存在

然而,通过将回调函数替换为箭头函数() => this.increment(),我们确保了箭头函数中的this指向了Counter实例,因此可以正确地调用this.increment()方法来更新count的值。

2.  不绑定arguments对象:没有自己的arguments对象。在箭头函数中访问arguments实际上会访问到它外层函数(如果有的话)的arguments对象。

3. 不可用作构造函数:由于箭头函数没有自己的this,并且没有prototype属性,因此它不能被用作构造函数。尝试使用new操作符调用箭头函数会抛出一个错误。

4. 不支持new.target:由于箭头函数不能用作构造函数,因此new.target在箭头函数中不可用。

5. 没有super:箭头函数同样没有super,super是用于在派生类中调用基类的方法或属性的。

补充:

1.  原型对象上尽量不要用箭头函数,例如:

function Person() {}
Person.prototype.walk = () => {
    console.log('坐地日行八万里')
    console.log(this)
}
const p1 = new Person()
p1.walk() //指向window 而不是p1.不知道是谁调用了

2. 箭头函数 不适用于:构造函数,原型函数,字面对象中函数,dom事件函数

--------------------------------------------------------------------------------------------------------------------------------

该文章为学习总结,若有错误或描述不当的地方,烦请评论或私信指正,万分感谢 😃

  • 13
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值