1.作用域及闭包
变量作用域
- 全局变量:(var) 声明在函数外部的变量(所有没有var直接赋值的变量都属于全局变量)
- 局部变量:(var) 声明在函数内部的变量(所有没有var直接赋值的变量都属于全局变量)
执行环境(execution context)
-
执行环境定义了变量或者函数有权访问的其他数据,每个执行环境都有一个与之关联的变量对象(variable object),执行环境中定义的变量和函数就保存在这个变量对象中;
-
全局执行环境是最外围的一个执行环境,通常被认为是window对象;
-
执行环境和变量对象在运行函数时生成;
-
执行环境中的所有代码执行完以后,执行环境被销毁,保存在其中的变量和函数也随之销毁;(全局执行环境到应用退出时销毁);
作用域链
- 当代码在一个执行环境中执行时,会创建变量对象的一个作用域链(scope chain),作用域链用来指定执行环境有权访问的所有变量和函数的访问顺序;
- 作用域链的最前端,始终是当前代码执行环境的变量对象,如果这个环境是函数,则其活动对象就是变量对象;
- 作用域链的下一个变量对象,来自外部包含环境,再下一个变量对象,来自下一个外部包含环境,以此类推直到全局执行环境;
- 在函数执行过程,根据当前执行环境的作用域链来逐层向外查找变量,并且进行标识符解析;
闭包
- 闭包,指的是词法表示包括不被计算的变量的函数,也就是说,函数可以使用函数之外定义的变量。
// 使用全局变量是一个简单的闭包实例
var sMessage = "hello world";
function sayHelloWorld() {
alert(sMessage);
}
sayHelloWorld();
//在一个函数中定义另一个会使闭包变得更加复杂
var iBaseNum = 10;
function addNum(iNum1, iNum2) {
function doAdd() {
return iNum1 + iNum2 + iBaseNum;
}
return doAdd();
} //这里,函数 addNum() 包括函数 doAdd() (闭包)。内部函数是一个闭包,因为它将获取外部函数的参数 iNum1 和 iNum2 以及全局变量 iBaseNum 的值。
//addNum() 的最后一步调用了 doAdd(),把两个参数和全局变量相加,并返回它们的和。
//这里的doAdd() 函数根本不接受参数,它使用的值是从执行环境中获取的
举例
<script>
function A(){
var x = 1;
return function(){
x++;
console.log(x);
}
}
var m1 = A();//第一次执行A函数
m1();//2
m1();//3
var m2 = A();//第二次执行A函数
m2();//2
m1();//4
</script>
// 每次执行A函数时,都会生成一个A的活动变量和执行环境,执行完毕以后,A的执行环境销毁。
// 但是活动对象由于被闭包函数引用,所以仍然保留,所以,最终剩下两个A的变量对象,因此m1和m2在操作x时,指向的是不同的数据。
1.为什么连续执行m1的时候,x的值在递增?
answer:因为m1在引用的活动对象A一直没有释放(想释放的话可以让m1=null),所以x的值一直递增。
2.定义函数m2的时候,为什么x的值重新从1开始了?
answer:因为又一次运行了A函数,生成一个新的A的活动对象,所以m2的作用域链引用的是一个新的x值。
3.m1和m2里面的x为什么是相互独立,各自维持的?
answer:因为在定义m1和m2的时候,分别运行了A函数,生成了两个活动对象,所以,m1和m2的作用域链是指向不同的A的活动对象的。
<script>
function A(){
var funs=[];
for(var i=0;i<10;i++){
funs[i]=function(){
return i;
}
}
return funs;
}
var funs = A();//定义funs[0]-funs[9],10个函数
console.log(funs[0]());//10
console.log(funs[1]());//10
console.log(funs[6]());//10
</script>
//执行环境和变量对象在运行函数时生成
//当执行var funs = A()时,只是定义函数,而没有执行,真正产生环境变量的时间是在console.log(funs[0]());
//当执行 console.log()时,此时A的变量对象中i值是什么呢?很简单,看它return的时候,i的值(10)
//所以,最后三句输出的都是10
(闭包部分参考以下文章)
2.this
- 作为对象方法调用,this 指代上级对象
var person = {
fname:'world',
lname:'hello',
fullname:function(){
console.log(this); // {fname: "world", lname: "hello", fullname: ƒ}this指向上级对象
return this.lname + ' ' +this.fname;
}
}
console.log(person.fullname()); //hello world
>------------------------------------------------------------------------------------------------------
var person = {
fname:'world',
lname:'hello',
a:{
fullname:function(){
console.log(this); // {fullname: ƒ}this指向上级对象
return this.lname + ' ' +this.fname;
}
}
}
console.log(person.fullname());
- 一般函数(全局函数,匿名函数),this 指代全局对象
var person = {
fname:'world',
lname:'hello',
fullname:function(){
console.log(this); // Window{}
return this.lname + ' ' +this.fname;
}
}
var get_full_name = person.fullname;
console.log(get_full_name()); // undefined
console.log(person.fullname()); // {fname: "world", lname: "hello", fullname: ƒ}
// hello world
- 作为构造函数调用,this 指代new 出的对象
function User(){
console.log(this); //Window{}
}
User();
>----------------------------------------
function User(){
console.log(this); //User {}
this.name = 'world';
this.age = 12;
}
var person = new User();
console.log(person); //User {name: "world", age: 12}
好用!!!动态!!!
function yo(){
console.log('Yo, I am ' + this.name );
}
var first = {
name:'hello',
}
var last = {
name:'world',
}
first.yo = yo;
last.yo = yo;
first.yo(); // Yo, I am hello
last.yo(); // Yo, I am world
//在不同环境下,给不同父级赋能
call, apply, bind()
function yo(name,a,b,c){
console.log('Yo, '+name+ '. I am ' + this.name+'! '+ a + b + c );
}
var first = {
name:'hello',
}
var last = {
name:'world',
}
yo.call(first,'BIBI',1,2,3); //(object,parm...)
yo.call(last,'CICI',3,2,1);
yo.apply(last,['CICI',3,2,1]); //(object,array[])
var last2 = yo.bind(last); // bind()返回一个新方法
last2('CICI',3,2,1);
3.构造函数与原型对象
构造函数
- 构造函数就是提供了一个生成对象的模板并描述对象的基本结构的函数。一个构造函数,可以生成多个对象,每个对象都有相同的结构。
- 总的来说,构造函数就是对象的模板,对象就是构造函数的实例。
构造函数的不足
/***所有的实例对象都可以继承构造函数中的属性和方法。***/
/***但是同一个构造函数的对象实例之间无法共享属性或方法。***/
function Person(name,height){
this.name=name;
this.height=height;
this.hobby=function(){
return 'watching movies';
}
}
var boy=new Person('keith',180);
var girl=new Person('rascal',153);
console.log(boy.name); //'keith'
console.log(girl.name); //'rascal'
console.log(boy.hobby===girl.hobby); //false
.prototype——解决构造函数的对象实例之间无法共享属性的不足
- js中每个数据类型都是对象(除了null和undefined),而每个对象都继承自另外一个对象,后者称为“原型”(prototype)对象。只有null除外,它没有自己的原型对象。
- 原型对象上的所有属性和方法,都会被对象实例所共享。
- 通过构造函数生成对象实例时,会将对象实例的原型指向构造函数的prototype属性。每一个构造函数都有一个prototype属性,这个属性就是对象实例的原型对象。
- prototype,对于构造函数来说,它是一个属性;对于对象实例来说,它是一个原型对象。
function Person(name,height){
this.name=name;
this.height=height;
}
Person.prototype.hobby=function(){
return 'watching movies';
}
var boy=new Person('keith',180);
var girl=new Person('rascal',153);
console.log(boy.name); //'keith'
console.log(girl.name); //'rascal'
console.log(boy.hobby===girl.hobby); //true
(构造函数与原型对象参考以下文章)