函数
函数声明:function fnName(){};
使用function关键字声明一个函数,在指定一个函数名。
- 函数声明后不能直接跟括号,会报错。可以用括号将函数声明括起来再调用:
(function fnName(){
alert('Hello World');
})();
函数表达式:var fnName = function(){};
使用function关键字声明一个匿名函数并赋予给一个变量。
- 函数表达式后面可以直接加括号,当JavaScript引擎解析到此处时能立即调用函数
var fnName = function(){
alert('Hello World');
}();
匿名函数:function(){};
匿名函数属于函数表达式,可用于赋予一个变量创建函数,赋予一个事件成为事件处理程序,或创建闭包等。
特殊情况
以下两种情况下,函数名为只读状态无法修改(在严格模式下修改会报错),且在函数外无法访问。
var b = 10;
(function b() {
b = 20;
console.log(b);//function b
})()
var a = function b(){
b = 10;
console.log(b);
}
a();//function b
b();//b is not defined
作用域
- 函数中新声明的变量均为局部变量
- while{...}、 if{...}、for(...){}、for(){...} 之内仅是代码块,而非局部作用域,因此其中声明的变量均为全局变量
-
let
、const
命令声明的变量只有在该变量所在的代码块内生效
var result = [];
for (var i = 0; i<10; i++){
result[i] = function () {
console.info(i)
}
}
console.log(i);//10
result[0]();//10
var array = [];
for (var i = 0; i < 3; i++) {
// 三个箭头函数体中的每个'i'都指向相同的绑定'3'。
array.push(() => i);
}
var newArray = array.map(el => el());
console.log(newArray); // [3, 3, 3]
使用let或闭包即可变为记录每一次的值
let array = [];
for (var i = 0; i < 3; i++) {
array[i] = (function(x) {
return function() {
return x;
};
})(i);
}
const newArray = array.map(el => el());
console.log(newArray); // [0, 1, 2]
if的括号()中声明的是局部变量,无法被外部获取
if (function fn(){
console.log(123)
}){
fn()//报错
}
闭包 (Closure)
闭包指的是:能够访问另一个函数作用域的变量的函数。(即在一个函数中声明的另一个函数)
闭包会携带包含它的函数的作用域,该作用域只有当闭包被销毁时才会销毁。
function a() {
var i = 0;
return function () {
console.log(i++)
}
}
var b = a()
b()//0
b()//1
var c = a()
c()//0
function generator(input) {
var index = 0;
return {
next: function() {
if (index < input.length) {
index += 1;
return input[index - 1];
}
return "";
}
}
}
var mygenerator = generator("hello");
mygenerator.next(); // returns "h"
mygenerator.next() // returns "e"
mygenerator = generator("world");
mygenerator.next(); // returns "w"
内存泄露问题
如果引用闭包的函数是一个全局变量,那么闭包会一直存在直到页面关闭;如果这个闭包以后不再使用的话,就会造成内存泄漏,需要手动清理
如果引用闭包的函数是个局部变量,等函数销毁后,在下次 JavaScript 引擎执行垃圾回收时,判断闭包这块内容如果已经不再被使用了,那么 JavaScript 引擎的垃圾回收器就会回收这块内存。
showId是个全局变量
function showId() {
var el = document.getElementById("app")
var id = el.id
el.onclick = function(){
aler(id) // 这样会导致闭包引用外层的el,当执行完showId后,el无法释放
}
el = null // 主动释放el
}
this指向
因函数作用域是在定义函数时生成的,不是在执行过程中生成的。因此this
指向函数定义时所处的作用域。
- 箭头函数,this指向函数定义时的上下文中的this
class中,static内上下文为类,其他上下文为实例 - 非箭头函数,有调用者则指向直接调用者,没有调用者则指向函数定义位置的全局对象(浏览器中为
windows
,node中为global
,严格模式下指向undefined
) - 全局执行上下文中的
this
也指向全局对象,如var o = {a:this}
- ()不影响直接调用者,如a.b()和(a.b)()直接调用者都是a
- 当函数作为参数使用
arguments[0]()
调用时,this
指向arguments
var length = 10;
function fn() {
console.log(this.length);
}
var obj = {
length: 5,
method: function(fn) {
arguments[0](); //2
fn(); //10 此时fn没有调用者
console.log(fn===arguments[0]) //true
}
};
obj.method(fn,1);
-
匿名函数的 this 总是指向 window , 通过
arguments.callee()
可以实现匿名函数的递归,此时 this 指向 arguments
var num = 10;
function A() {
this.num = 20;
this.say = function () {
var num = 30;
return function () {
console.log(this,this.num--)
if(this.num>=9){
arguments.callee();
}
}
}
}
var a = new A();
console.log(a.num);//20
a.say()();//Window 10
//arguments NaN
- 当
var a=obj.fn
,直接调用a()
时其中this
不再指向obj
而指向window
var baz=123;
var foo = {
bar: function () {
return this.baz;
},
baz: 1
};
var aaa=foo.bar;
(function (a) {
console.log(a());
})(foo.bar);//123
console.log(foo.bar())//1
console.log(aaa())//123
改变this指向
每个函数都从原型链继承了call
、apply
、bind
方法,即Function.prototype.call
、Function.prototype.apply
、Function.prototype.bind
。
- call 和 apply
改变this指向并立即执行。
第一个参数决定this指向(为null时指向window),其余为入参。区别在于call直接接受多个入参,而apply只接受一个所有入参组成的数组。
var a = {
user:"追梦子",
fn:function(e,ee){
console.log(this.user); //追梦子
console.log(e+ee); //3
}
}
var b = a.fn;
b();//undefined NaN
b.call(a,1,2);//追梦子 3
b.apply(a,[1,2]);//追梦子 3
- bind
返回一个修改后的函数,而不是直接将其执行。
第一个参数决定this指向(为null时指向window),其余为入参。
未传入的入参会作为新生成的函数的参数。
var a = {
user:"追梦子",
fn:function(e,d,f){
console.log(this.user); //追梦子
console.log(e,d,f); //10 1 2
}
}
var b = a.fn;
var c = b.bind(a,10);
c(1,2);