- 函数是对象,函数名也是变量,里面值存储的是函数的引用,且值是可以修改的
- 函数对象多了其他属性,如apply()、name
- 函数并没有签名,即调用函数的时候,参数是无法约束函数的
构造
- function f2(一堆参数){}
- const f3 = function(一堆参数) {}
- const f4 = (一堆参数)=>{}
- const f1 = new Function(一堆参数,函数体不需要大括号);
属性与方法
- name:存储着函数的标识符,即函数名的字符串化
- length:存储着函数命名参数的个数
- prototype:存储着函数实例的原型,指向Function.prototype,
箭头函数没有该属性 - apply/call():以指定的this来调用函数,前者接受this值和参数数组,后者接受this值和一堆参数、
- bind():返回新函数:原函数中this指定为传入参数的第一个
函数声明
function f2(一堆参数){}
1.函数名
- 函数名就是指向函数的指针,可以变换,也可以赋值给其他变量从而其他变量也指向了函数实例
函数名()则会调用函数,函数名则会访问函数对象实例
function f1(){
console.log('这是函数体');
}
f1();//调用函数
console.log(f1.name);//f1,访问的是函数实例
2.实名参数
- js既不关心参数类型,也不关心
参数数量 - 传递实名参数时,可以使用
拓展符- 调用时对数组使用拓展符,则会将数组分开传入
- 函数声明中使用拓展符,则会将对应的多个参数转为数组
const array = [1,2,3];
function f1(a,b){
console.log(a,b);
}
f1(array);//[ 1, 2, 3 ] undefined,参数数量少也可调用
f1(...array);//1 2,参数过多也可以调用
function f2(arg1,...args){
console.log(arg1,args);
}
f2(4,5,6);//4 [ 5, 6 ]
- 默认参数:默认参数中,前者不能使用后者来赋值,存在着暂时性死区
3.函数体
arguments
- 一个类数组
对象,存储着传入的参数,不是Array实例,但是Array操作基本上都能使用 - callee属性:指向arguments的函数实例的指针,用于递归,因为函数名可能中间会指向其他函数,所以不能像其他语言一样过度依赖函数名调用
function factorial(num){
if(num<=1) return 1;
return num*arguments.callee(num-1);
}
let trueFactorial = factorial;//传递函数对象引用
factorial = function(){//改变指向
return 0;
}
console.log(trueFactorial(5));//120
console.log(factorial(5));//0
this
指的是把函数当成方法调用的上下文对象,全局中则是global/window对象
global.color = 'red';
function sayColor(){
console.log(this.color);
}
const o = {
color:'blue',
sayColor
}
sayColor(); //red
o.sayColor(); //blue
//注意上述两者调用的函数是一样的,只是执行上下文环境不同
caller
指向调用当前函数的函数,如果在全局作用中调用这是null
new.target
如果函数new调用,则指向构造函数,否则为undefined,其实就是arguments.callee特供版
function Obj(){
console.log(new.target === arguments.callee);
}
new Obj();//true
函数表达式
const f3 = function(一堆参数) {}
- 与函数声明的区别
- 函数提升:函数声明是会将所有函数定义提升到最前面,而函数表达式并不会
箭头函数
const f4 = (一堆参数)=>{}
- 简写
- 参数只有一个时可不写括号
- 函数体只有一条语句时可不写大括号
- 与函数声明的区别
- 函数体不支持arguments
- 不能使用与创建对象相关的内容:super、new.target以及不存在属性prototype
- this指向的是定义时的上下文对象,而不是调用时
函数特殊使用
递归
正如前面而言,函数内部尽量不要使用函数名()来调用自身,而是使用arguments.callee(),但是在严格模式下会出错,所以使用如下取别名方法
const factorial = function f1(num){//取别名
if(num<= 1) return 1;
return num*f1(num-1);
}
console.log(factorial(5));//120
闭包
引用了另一个函数作用域中变量的
函数,通常是在一个函数内部定义并返回一个新函数(闭包),该新函数使用了外部函数的变量
function outer(value1,value2){
const temp = arguments;
return function(){
console.log(arguments);//只能访问本身的[Arguments] {}
console.log(temp);//访问外部函数的[Arguments] { '0': 1, '1': 2 }
return value1<value2;
}
}
const bibao = outer(1,2);
console.log(bibao());//true
bibao = null;//此时闭包函数对象被销毁,outer(1,2)活动对象才被销毁
- 问题:外部函数执行上下文的作用域链会销毁,但是活动对象依然保存在内存中,直到闭包函数对象被销毁
- 如果闭包函数希望访问外部函数的this和arguments,则需要先将这两个赋值给其他变量,才能在闭包中访问
尾调用
在外部函数return时调用另一个函数
- 这种情况一般会把外部函数保留在栈中,等到另一个函数返回之后再把外部函数弹出栈
- 优化条件:尾调用函数不是外部函数的闭包,且外部函数返回并不会对尾调用函数的返回进行额外操作
立即调用的函数表达式
此针对匿名函数,紧跟在第一组括号后面的第二组括号会立即调用前面的函数表达式
(()=>{console.log('diaoyong')})(); //diaoyong
私有变量/私有方法
js中并没有私有成员的概念,与之类似的有个私有变量:定义在函数或块中的变量,都可以认为是私有的
特权函数(对象中的方法)
能够访问函数私有变量及私有函数的公有方法,即在函数内部定义对象方法
- 定义特权方法的方式
- 直接使用this.特权方法
function Person(name){//name是私有变量 function sayName(){//私有方法,也是闭包 console.log(name); } this.getName = ()=>{//特权函数,也是闭包 sayName(); return name; } } const person1 = new Person('lisi'); console.log(person1.getName());//lisi lisi- 不足
- 由于直接使用this,所以外部函数就是构造方法,每次调用构造函数创建对象的时候都会重新创建一套变量和方法,这样会导致每个私有变量/私有方法都会在每个实例上创建,而没有做到共享
- 不足
- 定义在其他对象上
2.1 静态私有变量定义在构造函数的原型对象上
2.2 模块模式(function(name){ function sayName(){//私有方法,也是闭包 console.log(name); } Person = function(value){name = value};//构造函数,因为前面没有修饰符,所以是定义在全局的 Person.prototype.getName = ()=>{//特权函数,也是闭包 sayName(); return name; } })() const person1 = new Person('lisi'); console.log(person1.getName());//lisi lisi const person2 = new Person('zhangsan'); console.log(person1.getName());//zhangsan zhangsan,因为共享,所以跟着一起变 console.log(person2.getName());//zhangsan zhangsan定义在单例对象(只有一个实例的对象,一般使用对象字面量创建)上
2.3 模块增强模式let singleton = (function(name){ function sayName(){ console.log(name); } return {//直接返回单例对象 publicName:'public', getName(){//特权方法定义在单例对象中 sayName(); return name; } } })('lisi'); console.log(singleton.getName());//lisi lisi定义在自创建的对象上,同时对其进行增强(添加修改属性)
//singleton其实就是obj的增强版本:增加了静态变量 let singleton = (function(name){ function sayName(){ console.log(name); } let obj = new Object();//创建对象 obj.publicName = 'public';//对象增强 obj.getName = ()=>{//特权函数定义在对象上 sayName(); return name; } return obj;//返回对象 })('lisi'); console.log(singleton.getName());//lisi lisi