函数、作用域、闭包、this指向

函数

函数声明: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(){...} 之内仅是代码块,而非局部作用域,因此其中声明的变量均为全局变量
  • letconst命令声明的变量只有在该变量所在的代码块内生效
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指向

每个函数都从原型链继承了callapplybind方法,即Function.prototype.callFunction.prototype.applyFunction.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);
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容