闭包,this,作用域链,原型链

2,153 阅读7分钟

参考书:《JavaScript高级程序设计 第3版》

参考博客:www.jianshu.com/p/f8c7c5502…

执行环境

执行环境是Javascript中最为重要的一个概念。执行环境定义了变量或函数有权访问的其他数据,决定了它们各自的行为。每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中

全局执行环境一般是window对象,因此所有全局变量和函数都是作为window对象的属性和方法创建的。

某个执行环境中的所有代码执行完毕后,该环境被销毁,保存在其中的所有变量和函数定义也随之销毁。

全局执行环境直到应用程序退出----例如关闭网页或浏览器----时才会被销毁。

每个函数都有自己的执行环境。当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。而在函数执行之后,栈将其环境弹出,把控制权返回给之前的执行环境。

作用域链

当代码在一个环境中执行时,会创建变量对象的一个作用域链。作用域链的用途,是保证对执行环境有权访问的所有变量和函数的有序访问,作用域链的前端。始终都是当前执行代码所在环境的变量对象。

作用域链中的下一个变量对象来自包含(外部对象),再下一个变量对象则来自下一个包含对象,这样,一直延续到全局执行环境。

var color = "blue";

function changeColor() {
	if(color === "blue") {
		color = "red";
	} else {
		color = "blue"
	}
}

changeColor();

alert("Color is now " + color); // Color is now red

prototype

对于ECMAScript中的引用类型而言,prototype是保存它们所有实例方法的真正所在。在创建自定义引用类型和实现继承时,prototype属性的作用是极为重要的。

prototype属性是不可枚举的,因此不可用for-in发现。

call()和apply()

都用来在特定的作用域中调用函数,实际上等于设置函数体内this对象的值。

**call()**方法第一个参数是在其中运行函数的作用域,也就是this值,剩下的其余参数都直接传递给函数。

**apply()**方法第一个参数是this值,第二个参数是Array的实例,也可以是arguments对象。

改变this指向的4种方法

  1. call()

  2. apply()

  3. bind()

  4. 将this存储在变量中

new()方法做了什么

  1. 创建一个新对象

  2. 将新对象的__proto__指向构造函数的protptype对象

  3. 将构造函数的作用域赋值给新对象(也就是this指向新对象)

  4. 执行构造函数中的代码(为这个新对象添加属性)

  5. 返回新的对象

原型链

原型链是实现继承的一种主要方法。,其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。

每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。

只有构造函数有原型对象prototype,它是一个对象

原型对象中有个constructor属性,constructor指向构造函数本身,也只有原型对象具有constructor属性。

所有的对象都有_proto_属性,他也是个对象。

// 构造函数
function Student(name, age, sex){
	this.name = name;
	this.age = age;
	this.sex = sex;
	// 多个对象,会存储多个sayHi方法
	this.sayHi = function() {
		console.log('大家好,我是' + this.name);
	}
}

var s1 = new Student('lilei', 18, '男');
var s2 = new Student('hmm', 18, '女');

s1.sayHi();
s2.sayHi();

console.log(s1.sayHi === s2.sayHi);// false 两个独立的sayHi方法
// 构造函数
function Student(name, age, sex){
    this.name = name;
    this.age = age;
    this.sex = sex;
}

Student.prototype.sayHi = function() {
    console.log('大家好,我是' + this.name);
}

// 通过Student构造函数,创建的对象,可以访问Student.prototype中的成员
var s1 = new Student('lilei', 18, '男');
var s2 = new Student('hmm', 18, '女');

s1.sayHi();
s2.sayHi();

console.log(s1.sayHi === s2.sayHi); // true
alert(s1.__proto__ === Student.prototype) // true
alert(Student.prototype.__proto__ === Object.prototype) // true
alert(Object.prototype.__proto__ === null) // true
alert(Student.prototype.constructor === Student) // true

闭包

闭包是指有权访问另一个函数作用域中的变量和参数的函数,即使在其外部函数被返回(寿命终结)了之后。创建闭包的常见方式,就是在一个函数内部创建另一个函数。

闭包的优点

可以重复使用变量,并且不会造成变量污染。

全局变量可以重复使用,但是容易造成变量污染。局部变量尽在局部作用域内有效,不可以重复使用,不会造成变量污染。闭包结果结合了全局变量和局部变量的优点。

可以用来定义私有属性和私有方法。

function creFunc() {
    var result = new Array();
    for(var i=0;i<10;i++){
        result[i] = function() {
            return i;
        };
    }
    return result;
}
function creFuncs() {
	var result = new Array();
    for(var i=0;i<10;i++){
        result[i]=function(num){
            return function(){
                return num;
            }
        }(i);
    }
    return result;
}

闭包的缺点

比普通函数更占用内存,会导致网页性能变差。如果闭包的作用域链中保存着一个HTML元素,那么就意味着该元素无法被销毁。

function asHand() {
    var element = document.getElementById("someElement");
    element.onclick = function() {
        alert(element.id)
    };
}

上面的代码创建了一个作为element元素事件处理程序的闭包,而这个闭包则又创建了一个循环引用。其中的匿名函数保存了一个对asHand的活动对象的引用,因此就会导致无法减少element的引用数。只要匿名函数存在,element的引用数至少也是1,所以它所占用的内存就永远不会被回收。

function asHand() {
    var element = document.getElementById("someElement");
    var id = element.id;
    
    element.onclick = function(){
        alert(id);
    };
    element = null;
}

通过把element.id的一个副本保存在一个变量中,并且在闭包中引用该变量消除了循环引用。此外,闭包会引起包含函数的整个活动对象,在上面的代码中包括element。即使闭包不直接引用element,包含函数的活动对象中也仍然会保存一个引用。所以,需要把element变量设置为null,这样就可以解除对DOM对象的引用,顺利的减少其引用数,确保正常回收其占用的内存。

闭包的使用场景

1.小范围代替全局变量

2.访问私有变量的特权方法

3.setTimeout()调用函数

4.clear自身timer

闭包常见面试分析题

for (var i=0;i<3;i++){
    setTimeout(function(){
        console.log(i);
    },0);
    console.log(i);
}
0 1 2 3 3 3
for (var i=0;i<3;i++){
    setTimeout(function(){
        console.log(i++);
    },0);
    console.log(i);
}
0 1 2 3 4 5
for (var i=0;i<3;i++){
    setTimeout((function(){
        return function(){
            console.log(i++);
        };
    })(i),0);
    console.log(i);
}
0 1 2 3 4 5
console.log('start');
setTimeout((function(){
	console.log('立即执行');
	return function(){
		console.log('setTimeout');
	};
})(),0);
console.log('end');
start
立即执行
end
setTimeout

this

事件调用环境 谁触发事件,函数里面的this指向就是谁

全局环境 :window node环境:module.exports

函数内部

  1. this最终指向的是调用它的对象

    (1)普通函数直接调用与window调用

    (2)对象中的函数直接调用与window调用

  2. 函数被多层对象包含,如果函数被最外层对象调用,this指向的也只是它上一级的对象

    (1)多层对象中的函数的this指向

    (2)对象中的this指向

  3. 如果构造函数中有return,如果return的是对象,this指向返回的对象,如果不是对象,则this指向保持原来的规则,在这里,null比较特殊,也保持原来的规则。

箭头函数的this指向

箭头函数本身是没有this和arguments的,在箭头函数中this实际上调用的是定义的上一层作用域的this,这里强调一下是上一层作用域,因为对象是不能形成独立的作用域的。