函数也是一个特殊的对象
定义一个函数
// 具名函数
function 函数名(arg1, arg2, ...){
语句
return 返回值
}
// 匿名函数——把上面的具名函数的函数名去掉
let a = function 可有可无的函数名(x, y){ return x+y }
匿名函数也称作函数表达式。如果以上面的方式明明一个函数,则是把函数的内存地址赋值给变量a,同时该函数作用域只局限在该表达式中,如果不通过a调用,单独调用该函数则会报错。如果去掉let a =,则匿名函数作用域为全局作用域。这是比较特殊的一点。
// 箭头函数
let f1 = x = > x*x;
let f2 = (x, y) => x+y;
let f3 = (x, y) => {return x+y} // 有return关键字时花括号不能省
let f4 = (x, y) => ({name: x, age: y}) //直接返回对象会出错,需要加个圆括号
// 用构造函数(基本没人用)
let f = new Function('x', 'y', 'return x+y')
所有函数都是Function构造出来的,包括Object、Array、Function也是
One step further
let fn = () => console.log('hi')
let fn2 = fn;
fn2()
上述代码:
fn保存了匿名函数的地址- 这个地址被复制给了
fn2 fn2()调用了匿名函数fn和fn2都是匿名函数的引用- 真正的函数既不是
fn也不是fn2
函数的要素
setTimeout()函数的作用通俗地理解为:先把手头的事情忙完,再赶紧执行我。
全局变量&局部变量
- 在顶级作用域声明的变量是全局变量
- window的属性是全局变量
- 其他都是局部变量
函数可嵌套,作用域也一样
function f1(){
let a = 1;
function f2(){
let a = 2;
console.log(a);
}
console.log(a);
a = 3;
f2();
}
f1() // 先输出1,在输出2
作用域规则:如果多个作用域有同名变量a
- 那么查找
a的声明时,就向上取最近的作用域,简称[就近原则] - 查找
a的过程与函数执行无关,但a的值与函数执行有关
闭包
定义:如果一个函数用到了外部的变量,那么这个函数加这个变量就叫做闭包
闭包先讲定义,之后的笔记再讲用途
举例:
function f1(){
let a = 1;
function f2(){
// -------闭包-------
let a = 2
function f3(){
console.log(a)
}
// -------闭包-------
a = 22;
f3();
}
console.log(a);
a = 100;
f2();
}
f1
形式参数
简称形参。
function add(x, y){
return x+y
}
add(1, 2)
其中x和y是形参,因为并不是实际的参数(形参可认为是声明变量,实际上是一种语法糖)。当调用add方法时,1和2就是实际参数,简称实参。
形参可多可少,查看下面的例子
function add(x){
return x+ arguments[1]
}
add(1, 2)
function add(x, y, z){
return x+y
}
返回值
每个函数都有返回值,函数执行完了才会返回,只有函数有返回值。
function hi(){ console.log('hi'); } // 没写return,所以返回值是undefined
function hi(){ return console.log('hi'); } // 返回值为console.log('hi')的值,即undefined
调用栈
- JS引擎再调用一个函数前,需要把函数所在的环境
push到一个数组里 - 这个数组叫做调用栈
- 等函数执行完了,就会把环境
pop出来 - 然后
return到之前的环境,继续执行后续代码
arguments
arguments是函数最重要的两要素之一。
和this合起来说。
this
this是函数最重要的两要素之二,也是JS三座大山的第二座。
function fn(){ console.log(arguments) }
fn(1, 'b')
查看fn(1, 'b')返回的__proto__属性,也就是原型,可知arguments是一个包含所有实际参数的伪数组,因为构造函数是Object
function fn(){ console.log(this) }
如果函数不给任何条件,则this默认指向window。
this就是提供一种调用自身对象的方法,因为在没有实例化对象之前,我们不知道实例的名称,所以采用这么一个方法,方便写代码。这样讲的话,其实可以把this理解为一种隐藏的形参。
多的不想讲了。。。这个我会。看一个图吧,可以知道python传递对象的方法有多土了。
let person = {
name: 'frank',
sayHi(){ console.log(this.name); }
}
person.sayHi() // 传this的小白写法
person.sayHi.call(person) // 传this的js大师写法
person.sayHi.call({name: 'jack'}) // 对象里的函数和对象本身其实没有什么关系,只是恰好组合在了一起。
如何传this? 可以用fn.call(this, *arguments)传this和arguments。需要注意的是:this实参不是对象时,会自动地被封装成相应的对象
Array.prototype.forEach2 = function(){
console.log(this);
}
let array = [1, 2, 3];
array.forEach2.call(array); // [1, 2, 3]
array.forEach2(); // [1, 2, 3]
// 实现Array原型的forEach方法
Array.prototype.forEach2 = function(fn){
for(let i = 0; i < this.length; i++){
fn(this[i], i);
}
}
使用这个大师调法时传递的this,可以是任意的值,最终是一个对象;使用小白写法,实参默认就是this。所以可以理解:小白写法是隐式传递默认的this,大师写法是显式传递可以为任意的this。所以下面的代码可以运行
array.forEach2.call(array, (item)=>console.log(item))
下面的代码可以帮助理解
// this的两种使用方法
// 隐式传递
fn(1, 2) // 等价于fn.call(undefined, 1, 2)
obj.child.fn(1) // 等价于obj.child.fn.call(obj.child, 1)
// 显式传递
fn.call(undefined, 1, 2)
fn.apply(undefined, [1, 2]) // 调用apply方法时,参数为数组形式
绑定this: 使用bind方法可以让this不被改变
function f1(p1, p2){
console.log(this, p1, p2)
}
let f2 = f1.bind({name: 'frank'}) // f2就是f1绑定了this之后的新函数
f2() // 等价于 f1.call({name: 'frank'})
多选题再加深理解:
箭头函数
箭头函数没有arguments和this。
箭头函数里面的this就是外面的this
console.log(this) //就是默认的window
let fn = () => console.log(this)
fn() // window
如果你修改this的指向,那么调用fn函数打印出来的就不再是window了。
let fn = () => console.log(arguments)
fn(1, 2, 3) // 会报错,因为箭头函数没有arguments