函数声明与表达式
我们先来看一个栗子
foo(); // 正常运行,因为foo在代码运行前已经被创建
function foo() {}
复制代码
方法会在执行前被解析,因此它存在于当前上下文的任意一个地方, 即使在函数定义体的上面被调用也是对的。
函数赋值表达式
foo; // 'undefined'
foo(); // 出错:TypeError
var foo = function() {};
复制代码
由于 var 定义了一个声明语句,对变量 foo 的解析是在代码运行之前,因此 foo 变量已经被定义过了。 但是由于赋值语句只在运行时执行,因此在相应代码执行之前, foo 的值缺省为 undefined。
如果内部函数和外部函数的变量名重名
JavaScript的函数在查找变量时从自身函数定义开始,从“内”向“外”查找。如果内部函数定义了与外部函数重名的变量,则内部函数的变量将“屏蔽”外部函数的变量。
this 的工作原理(划重点!)
this在js中一共有五种不同的情况
1.全局范围内:当在全部范围内使用 this,它将会指向全局对象。
2.函数调用:这里 this 也会指向全局对象。
foo();
复制代码
3.方法调用:这个例子中,this 指向 test 对象。
test.foo();
复制代码
4.调用构造函数:如果函数倾向于和 new 关键词一块使用,则我们称这个函数是 构造函数。 在函数内部,this 指向新创建的对象。
new foo();
复制代码
5.显式的设置this:
function foo(a, b, c) {}
var bar = {};
foo.apply(bar, [1, 2, 3]); // 数组将会被扩展,如下所示
foo.call(bar, 1, 2, 3); // 传递到foo的参数是:a = 1, b = 2, c = 3
复制代码
当使用 call 或者 apply 方法时,函数内的 this 将会被 显式设置为函数调用的第一个参数,在foo 函数内 this 被设置成了 bar。
补充!!:
1.作为函数直接调用,非严格模式下,this指向window,严格模式下,this指向undefined;
2.作为某对象的方法调用,this通常指向调用的对象。
3.使用apply、call、bind 可以绑定this的指向。
4.在构造函数中,this指向新创建的对象
5.箭头函数没有单独的this值,this在箭头函数创建时确定,它与声明所在的上下文相同。
如果对一个函数进行多次 bind,那么上下文会是什么呢?
let a = {}
let fn = function () { console.log(this) }
fn.bind().bind(a)() // => ?
复制代码
不管我们给函数 bind 几次,fn 中的 this 永远由第一次 bind 决定,所以结果永远是 window。
为了方便记忆这里给出一张图
了解了这些小知识,下面我们再来看一个栗子
Foo.method = function() {
function test() {
// this 将会被设置为全局对象
}
test();
}
复制代码
我们以为test中的this会指向Foo对象,其实不是。那么我们怎么要解决这个问题呢?通常是在函数外部写一个var that = this,that只是我们随意起的名字,不过这个名字被广泛的用来指向外部的 this 对象。
Foo.method = function() {
var that = this;
function test() {
// 使用 that 来指向 Foo 对象
}
test();
}
复制代码
为了在 test 中获取对 Foo 对象的引用,我们需要在 method 函数内部创建一个局部变量指向 Foo 对象。
除了这样解决,在es6的新特性里添加了箭头函数,它能很好的解决这个问题。另外箭头函数还用简化代码量的特点。
什么是箭头函数
() => console.log('Hello')
复制代码
特点:
1.不绑定this(箭头函数不会创建自己的this,它只会从自己的作用域链的上一层继承this)
2.不绑定arguments(在箭头函数中我们是不能直接使用arguments对象的,得不到结果)
3.更简化的代码语法
注意:即使有这么多好处,但同时最好不要在定义对象方法、定义原型方法、定义构造函数、定义事件回调函数中使用箭头函数。
闭包
当内部函数被保存到外部时,将会生成闭包。生成闭包后,内部函数依旧可以访问其所在的外部函数的变量。
闭包问题的解决方法:立即执行函数、let
详细解释:
当函数执行时,会创建一个称为执行期上下文的内部对象(AO),执行期上下文定义了一个函数执行时的环境。
函数还会获得它所在作用域的作用域链,是存储函数能够访问的所有执行期上下文对象的集合,即这个函数中能够访问到的东西都是沿着作用域链向上查找直到全局作用域。
函数每次执行时对应的执行期上下文都是独一无二的,当函数执行完毕,函数都会失去对这个作用域链的引用,JS的垃圾回收机制是采用引用计数策略,如果一块内存不再被引用了那么这块内存就会被释放。
但是,当闭包存在时,即内部函数保留了对外部变量的引用时,这个作用域链就不会被销毁,此时内部函数依旧可以访问其所在的外部函数的变量,这就是闭包。
先看两个例子,两个例子都打印5个5
for (var i = 0; i < 5; i++) {
setTimeout(function timer() {
console.log(i)
}, i * 100)
}
function test() {
var a = [];
for (var i = 0; i < 5; i++) {
a[i] = function () {
console.log(i);
}
}
return a;
}
var myArr = test();
for(var j=0;j<5;j++)
{
myArr[j]();
}
复制代码
解决方法: 使用立即执行函数
for (var i = 0; i < 5; i++) {
;(function(i) {
setTimeout(function timer() {
console.log(i)
}, i * 100)
})(i)
}
function test(){
var arr=[];
for(i=0;i<10;i++)
{
(function(j){
arr[j]=function(){
console.log(j);
}
})(i)
}
return arr;
}
var myArr=test();
for(j=0;j<10;j++)
{
myArr[j]();
}
复制代码
闭包-封装私有变量
function Counter() {
let count = 0;
this.plus = function () {
return ++count;
}
this.minus = function () {
return --count;
}
this.getCount = function () {
return count;
}
}
const counter = new Counter();
counter.puls();
counter.puls();
console.log(counter.getCount())
复制代码
待补充,持续更新中...
注:参考部分引自JavaScript 秘密花园