创建函数
函数是一种特殊的函数
// 具名函数
function fn(x, y) {
return x + y
}
// 匿名函数
function (x, y) {
return x + y
}
// 箭头函数
let a = x => x * x
let a2 = (x, y) => {
console.log('hi!');
return x + y
}
let a3 = x => ({
name: x
})
函数的要素
调用时机
let i = 0
for(i = 0; i<6; i++){
setTimeout(()=>{
console.log(i)
},0) // 马上处理完循环 再执行 console.log(i)
}
// 会打印 6 个 6
我们先了解setTimeout()
方法:它是设置一个定时器,该定时器在定时器到期后执行一个函数或指定的一段代码。setTimeout()
是异步任务 for循环是同步任务,只有先把同步任务执行完在执行异步任务。这里就是先把循环执行完 ,执行完循环后的 i = 6
,然后再执行setTimeout
里面的箭头函数。打印出i
也就是6 。也就是打印出6个6。
让他打印 0、1、2、3、4、5
执行下面代码
for( let i = 0; i<6; i++){
setTimeout(()=>{
console.log(i)
},0)
}
在for循环中使用let的情况下,由于块级作用域的影响,导致每次迭代过程中的 i 都是独立的存在。也就是说这6次循环i
独立出了 0、1、2、3、4、5、6
这7个数 。
其他方法
function fn1() {
for (var i = 0; i < 6; i++) {
function fn2(i) {
setTimeout(() => {
console.log(i)
}, 0)
}
fn2(i);
}
}
我的理解如下这里fn2
引用了外面函数的变量形成了闭包,在闭包的状态下使得变量i始终保存在内存中,每一次调用都是在上一次调用的基础上进行计算。
作用域
JavaScript 只有两种作用域:一种是全局作用域,变量在整个程序中一直存在,所有地方都可以读取;另一种是函数作用域,变量只在函数内部存在。
在顶级作用域申明的变量是全局变量
window的属性是全局变量
其他的都是局部变量
局部变量
function fn(x){
let a = 1
x=a
}
console.log(a);
// a 会报错 a is not defined
全局变量
var a = 1;
function f() {
console.log(a);
}
f()
// 1
作用域与函数执行无关的是静态作用域
var a = 1;
var x = function () {
console.log(a);
};
function f() {
var a = 2;
x();
}
f() // 1
函数x是在函数f外部声明的,所以他的作用域绑定外层 函数x 的内部变量是不会在f函数内部取值。
闭包
闭包简单理解成“定义在一个函数内部的函数”。
如果一个函数用到了外部的变量,那么这个函数加这个变量就叫做闭包。
正常情况下,函数外部无法读取函数内部声明的变量。如果出于种种原因,需要得到函数内的局部变量。正常情况下,这是办不到的,只有通过变通方法才能实现。那就是在函数的内部,再定义一个函数。
父对象的所有变量,对子对象都是可见的,反之则不成立。
function f1(){
let a = 1
function f2(){
let a = 2
function f3(){
console.log(a) // 这里的a 调用了外面的a
}
a = 22
f3()
}
console.log(a)
a = 100
f2()
}
f1()
// 1
// 22
上面代码只解释以下 f2 和 f3 ;函数f3在函数f2里面 这时 f2 内部的所有局部变量对f3是可见的。
既然f3可以读取f2的局部变量,那么只要把f3作为返回值,我们不就可以在f1外部读取它的内部变量。
闭包的最大用处有两个,一个是可以读取外层函数内部的变量,另一个就是让这些变量始终保持在内存中,即闭包可以使得它诞生环境一直存在。
function fn(item) {
return function () {
return item++;
};
}
var inc = fn(5);
inc() // 5
inc() // 6
inc() // 7
上述代码 item是fn 的内部变量 ,在闭包的状态下被保留 ,每一次调用都是在上一次调用的基础上进行计算。闭包inc使得函数fn的内部环境,一直存在。所以,闭包可以看作是函数内部作用域的一个接口。
形式参数
函数运行的时候,有时需要提供外部数据,不同的外部数据会得到不同的结果,这种外部数据就叫参数。
function add(x, y){
return x+y
}
// 其中 x 和 y 就是形参,因为并不是实际的参数
add(1,2)
// 调用 add 时,1 和 2 是实际参数,会被赋值给 x y
形参的本质就是变量申明
返回值
每个函数都有返回值 执行完才有返回值
没有写return 就会返回 undefined
调用栈
什么是调用栈
- JS 引擎在调用一个函数前
- 需要把函数所在的环境 push 到一个数组里
- 这个数组叫做调用栈
- 等函数执行完了,就会把环境弹(pop)出来
然后 return 到之前的环境,继续执行后续代码
爆栈
如果调用栈中压入的帧过多,程序就会崩溃
函数名提升
f()
function fn(){}
// 不管你把具名函数声明在哪里,它都会跑到第一行
表面上,上面代码好像在声明之前就调用了函数f。但是实际上,由于“变量提升”,函数f被提升到了代码头部,也就是在调用之前已经声明了。但是,如果采用赋值语句定义函数,JavaScript 就会报错。
f();
var f = function (){};
// TypeError: undefined is not a function
var f;
f();
f = function () {};
上面代码第二行,调用f
的时候,f
只是被声明了,还没有被赋值,等于undefined
,所以会报错。
arguments(除了箭头函数)
arguments 是一个伪数组
由于 JavaScript 允许函数有不定数目的参数,所以需要一种机制,可以在函数体内部读取所有参数。这就是arguments对象的由来。
this(除了箭头函数)
如果不给this任何条件 this会默认指向window
function fn(){
console.log(this)
}
fn()
// 输出 Window {window: Window, self: Window, document: document, name: "", location: Location, …}
如果传的这个this不是对象那个JS会自动封装成一个对象
目前可以用 fn.call(xxx, 1,2,3) 传 this 和 arguments
function fn(){
'use strict' ; // 让this 不要乱来 开启严格模式 这里就不会变成对象
console.log(this)
}
this是隐藏参数
arguments是普通参数
let person = {
name: 'frank',
sayHi(this){
console.log(`你好,我叫` + this.name)
}
}
person.sayHi()
//相当于
person.sayHi(person)
//person.sayHi()会隐式地把 person 作为 this 传给 sayHi
//然后 person 被传给 this 了(person 是个地址)
//这样,每个函数都能用 this 获取一个未知对象的引用了
this就是调用函数的对象
.call
function add(x,y){
return x+y
}
add.call(null,1,2)
为什么要多写一个 undefined
- 因为第一个参数要作为 this
- 但是代码里没有用 this
- 所以只能用 undefined 占位
- 其实用 null 也可以
Array.prototype.forEach = function(){
console.log(this);
}
let arr = [1,2,3]
arr.forEach.call(arr) // 这里的arr就是this
// forEach 源代码 遍历当前数组
Array.prototype.forEach = function(fn){
for(let i = 0;i<this.length;i++){
fn(this[i],i)
}
}
arr.forEach.call(arr.(item)=> conaole.log(item))
箭头函数
console.log(this) // window
let fn = () => console.log(this)
fn() // window
立即执行函数
!function () { /* code */ }();
~function () { /* code */ }();
-function () { /* code */ }();
+function () { /* code */ }();