1.两种创建函数方法的比较。
fn ();
function fn() {
//声明,用这种写法时,fn()在它之前的话不会报错,执行到这里的时候,他会自动把函数声明这段代码提到前面去执行
console.log(1);
}
fn1 ();
var fn1 = function() {
//表达式,用这种写法时,fn1()在它之前的话会报错,undefined.
console.log(2)
}
小知识:
age = 10;
console.log(age);
var age; //这种写法其实是一种占位,执行到age = 10的时候,他会把变量声明给提前,从而会打印出10.
2.作用域和闭包。
- 执行上下文
先从下面代码中来理解下执行上下文:
1. console.log(a); //a is not defined
2. console.log(a); //undefined
var a;
3. console.log(a); //undefined
var a = 100;
第1个console.log(a),结果为a is not defined;第2个和第3个结果都是undefined。可以发现浏览器在未执行console.log(a)之前就已经知道了a是undefined,但是不知道a是100。
在一段js代码拿过来真正一句一句运行之前,浏览器已经做了一些“准备工作”,其中就包括对变量的声明,而不是赋值。变量赋值是在赋值语句执行的时候进行的。可用下图模拟:
这是第一种情况。
下面还有。先来个简单的。
有js开发经验的朋友应该都知道,你无论在哪个位置获取this,都是有值的。至于this的取值情况,比较复杂,会专门拿出一篇文章来讲解。
与第一种情况不同的是:第一种情况只是对变量进行声明(并没有赋值),而此种情况直接给this赋值。这也是“准备工作”情况要做的事情之一。
下面还有。。。第三种情况。
在第三种情况中,需要注意代码注释中的两个名词——“函数表达式”和“函数声明”。虽然两者都很常用,但是这两者在“准备工作”时,却是两种待遇。
看以上代码。“函数声明”时我们看到了第二种情况的影子,而“函数表达式”时我们看到了第一种情况的影子。
没错。在“准备工作”中,对待函数表达式就像对待“ var a = 10 ”这样的变量一样,只是声明。而对待函数声明时,却把函数整个赋值了。
好了,“准备工作”介绍完毕。
我们总结一下,在“准备工作”中完成了哪些工作:
- 变量、函数表达式——变量声明,默认赋值为undefined;
- this——赋值;
- 函数声明——赋值;
这三种数据的准备情况我们称之为“执行上下文”或者“执行上下文环境”。
- this
this要在执行的时候才能确认值,定义的时候无法确认。(执行时是指函数后面加‘()‘,否则就是处于定义状态)
var a = {
name: 'A',
fn: function () {
console.log(this.name);
}
}
a.fn(); //this === a
a.fn.call({name: 'B'}); //this === {name: 'B'}
var fn1 = a.fn;
fn1(); //this === window
总结以下几种执行场景:
1.作为构造函数执行。
function Foo(name) {
this.name = name; //这里的this.name = name之前,省略了this.name = {},之后省略了return this.
}
var f = new Foo("zhangsan");
2.作为普通函数执行。(这时this是指window)
3.作为对象属性执行。( 如a.fn() )
4.使用call apply bind这些函数,
function fn (name, age) {
alert(name);
console.log(this);
/*这里使用的是Call这个函数,所以这里this指的是{a:10}这个对象,然后后面那两个参数分别对应name和age*/
}
fn.call({a:10}, "zhangsan", 20);
同理,apply也一样,只不过它后面的参数是通过数组来传递,实际工作中使用call这种写法比较多
fn.apply({a:10}, ["zhangsan", 20]);
bind:
var fn = function (name, age) {
alert(name);
console.log(this);
}.bind({x: 100});
fn("zhangsan", 25); //正常我们如果不加bind({x: 100})的话,this指的是window,加了以后this就是指{x: 100}这个对象。
- 作用域
1.js没有块级作用域。
//js无块级作用域
if (true) {
var one = 'zhangsan';
}
console.log(one);
//上面相当于下面这个,实际过程中为了可读性,推荐使用下面这种写法。
var two;
if (true) {
two = 'lisi';
}
console.log(two);
2.js有函数作用域和全局作用域。
var a = 100; //这就是全局作用域,在任何地方都能够使用a。
function fn() {
var b = 20;
console.log(b); //20
}
fn();
console.log(b); //报错,b is not defined,这是由于函数作用域,b是在函数内部定义的,函数以外我们使用不了它。
- 作用域链
一个自由变量一层一层的往它的父级作用域去找它,所形成的链式结构就称为作用域链。
函数在哪里定义,他的父级作用域就在哪。
var a = 100;
function fn () {
var b = 200;
//当前作用域没有定义的变量,即‘自由变量’。如下面的a,在fn函数作用域内没有定义这个变量,所以a是自由变量。
console.log(a);
console.log(b);
}
fn();
var a = 100;
function fn () {
var b = 200;
function fn1 () {
var c = 300;
console.log(a); //a是自由变量
console.log(b); //b是自由变量
console.log(c);
}
fn1();
}
fn();
- 闭包
闭包的使用场景:
1.函数作为返回值。
function F1() {
var a = 100;
//返回一个函数(函数作为返回值)
return function () {
console.log(a); //100
}
}
//f1得到一个函数
var f1 = F1();
var a = 200;
f1();
2.函数作为参数传递
function F1() {
var a = 100;
return function () {
console.log(a);
}
}
var f1 = F1();
function F2(fn) {
var a = 200;
fn(); //打印出100,因为执行fn()的时候,即执行F1()函数,a是一个自由变量,应该去他的父级作用域去查找,即定义a的作用域查找,所有打印出100.
}
F2(f1);
问题1:说一下对变量提升的理解。
- 变量定义
- 函数声明(注意和函数表达式的区别)
问题2:说明this的几种不同的使用场景。
- 作为构造函数执行
- 作为对象属性执行
- 作为普通函数执行
- call apply bind
问题3:创建10个a标签,点击的时候弹出对应的序号。
//错误的写法
var i, a;
for(i=0; i<10; i++) {
a=document.createElement('a');
a.innerHTML = i + '<br/>';
a.addEventListener('click', function(e) {
e.preventDefault();
alert(i); //i是自由变量,去定义他的位置找,定义他的位置在全局变量里,此时for循环已经结束,所以i的值是10
})
document.body.appendChild(a);
}
//正确的写法
var i;
for(i=0; i<10; i++) {
(function (i) {
var a=document.createElement('a');
a.innerHTML = i + '<br/>';
a.addEventListener('click', function(e) {
e.preventDefault();
alert(i); //i是自由变量,去定义他的位置找,即父作用域获取值,定义他的位置在全局变量里,此时for循环已经结束,所以i的值是10
})
document.body.appendChild(a);
})(i);
} //上面那种括号里面的叫自执行函数,就是不用调用,只要定义完成,立即执行的函数
问题4:如何理解作用域。
- 自由变量
- 作用域链,即自由变量的查找
- 闭包的两个场景
问题5:实际开发过程中闭包的应用。
//闭包实际应用中主要用于封装变量,收敛权限
function isFirstLoad() {
var list = [];
return function (id) {
if (list.indexOf(id) >= 0) {
console.log(false);
} else {
list.push(id);
console.log(true);
}
}
}
var firstLoad = isFirstLoad();
firstLoad(10); //true
firstLoad(10); //false
firstLoad(20); //true