目录
函数基础
在JavaScrip中,引用数据类型只有Object;而函数是一种特殊的对象。
函数声明
写法
function 函数名(形参列表){
//函数体
}
//函数表达式
var 函数名 = function(形参列表){
//函数体
}
声明提升
- 函数声明与var声明的变量类似,也存在声明提升,即在声明之前也可以使用
fn1(); //输出:"具名函数",fn1
function fn1(){
// 函数声明,声明提升
console.log("具名函数",fn1.name);
}
fn2() //报错, const不具备变量提升,fn2声明赋值前不能调用
const fn2 = function(){
// 函数表达式。(是赋值语句的一部分),将匿名函数赋值给fn2
console.log("匿名函数");
}
函数内部属性
-
函数内部属性只能在函数内部才能访问
arguments
- arguments(动态参数)是一个类数组对象(伪数组),除了length和索引外,不能用任何数组的方法
- 它包含传递给函数的参数,是所有(非箭头)函数中都可用的局部变量
-
它并不总是与函数参数列表保持同步,例如,如果你更改了 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)
特点
- 它是一个匿名函数,被包含在圆括号
()
中 - 立即执行,函数定义后面跟着一对圆括号
()
,这对圆括号的作用是调用前面定义的函数 - 页面加载完成后只执行一次的设置函数
- 创建一个独立的作用域,使得函数内部定义的变量不会影响到全局作用域
举例
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__在关系图中应该是怎么样的呢?
总结
- 每个对象均存在隐式原型(__proto__),“函数对象”才有prototype属性
- __proto__存在的意义在于为原型链查找提供方向,原型链查找靠的是__proto__,而不是prototype
- “函数对象”的__proto__都指向Function.prototype
- 每个对象都有一个隐式原型属性(__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事件函数
--------------------------------------------------------------------------------------------------------------------------------
该文章为学习总结,若有错误或描述不当的地方,烦请评论或私信指正,万分感谢 😃