作用域
1、理解
- 就是一块地盘,一个代码所在的区域
- 它是静态的(相对于上下文对象),在编写代码时就确定了
2、分类
- 全局作用域
- 函数作用域
- 没有块作用域(ES6有了)
3、作用
- 隔离变量,不同作用域下同名的变量不会有冲突
作用域与执行上下文
1、区别一
- 全局作用域之外,每个函数都会创建自己的作用域,作用域在函数定义时就已经确定了,而不是在函数调用时
- 全局执行上下文环境是在全局作用域确定之后,js代码马上执行之前创建的
- 函数执行上下文是在调用函数时,函数体代码执行之前创建的
2、区别二
- 作用域是静态的,只要函数定义好就一直存在,且不会在变化
- 执行上下文是动态的,调用函数时创建,函数调用结束时就会自动释放
3、联系
- 执行上下文(对象)是从属于所在的作用域
- 全局上下文环境==>全局作用域
- 函数上下文环境==>对应的函数使用域
作用域链
1、理解
- 多个上下级关系的作用域形成的链,它的方向是从下向上的(从内到外)
- 查找变量就是沿着作用域链来查找的
2、查找一个变量的查找规则
- 在当前作用域下的执行上下文中查找相应的属性,如果有直接返回,否则进入2
- 在上一级作用域的执行上下文中查找相应的属性,如果有直接返回,否则进入3
- 再次执行2的相同操作,直到全局作用域,如果还找不到就抛出找不到的异常
var a = 1;
function fn1(){
var b = 2;
function fn2(){
var c = 3;
console.log(c);//3
console.log(b);//2
console.log(a);//1
console.log(d);//报错
};
fn2();
};
fn1();
作用域面试题
1、测试题一
var x = 10;
function fn(){
console.log(x);
};
function show(f){
var x = 20;
f();
};
show (fn);//输出10
/*在定义函数时,作用域已经确定了,往fn的上一级找*/
2、测试题二
var fn = function(){
console.log(fn);
};
fn();//输出函数体function(){console.log(fn);}
var obj = {
fn2: function(){
console.log(fn2);
}
};
obj.fn2();//报错,因为fn2是obj的属性,不是变量,作用域中找不到
/*如果是console.log(this.fn2),则会有结果*/
闭包理解
1、如何产生闭包
- 当一个嵌套内部(子)函数引用了嵌套外部(父)函数的变量(或函数)时,就产生了闭包
2、闭包理解
- 使用chrome调式查看
- 理解一:闭包是嵌套的内部函数
- 理解二:包含被引用变量(或函数)的对象
- 注意:闭包存在于嵌套的内部函数中
3、产生闭包的条件?
- 函数嵌套
- 内部函数引用了外部函数的数据(变量/函数)(重要)
- 外部函数调用
function fn() {
var a = 2;
var b = 'zbc';
function fn1() {//执行函数定义就会产生闭包
console.log(a);
};
return fn1;//加这个可以在当前版本浏览器中源代码查看fn1
};
fn();
常见的闭包
1、将函数作为另一个函数的返回值
function fn1(){
var a = 2;
function fn2(){
a++;
console.log(a);
};
return fn2;
};
var f = fn1();
f();//3
f();//4
2、将函数作为实参传递给另一个函数调用
function showDelay(msg,time){
setTimeout(
function (){
alert(msg) },time);
};
showDelay('lyh',2000);
闭包的作用
1、使用函数内部的变量在函数执行完后,仍然存活在内存中(延长了局部变量的生命周期)
2、让函数外部可以操作到函数内部的数据
3、问题
- 函数执行完后,函数内部声明的局部变量是否还存在?
一般是不存在,存在于闭包中的变量才可能存在
- 在函数外部能直接访问函数内部的局部变量吗?
不能,但我们可以通过闭包在外部操作它
闭包的生命周期
1、产生:在嵌套函数定义执行完时就产生了(不是在调用)
2、死亡:在嵌套的内部函数成为垃圾对象时
function fn1(){
//此时闭包就已经产生了(函数提升,内部函数对象已经创建了)
var a = 2;
function fn2(){
a++;
console.log(a);
};
return fn2;
};
var f = fn1();
f();//3
f();//4
f = null;//闭包死亡(包含闭包的函数对象成为垃圾对象)
闭包的缺点及解决
1、缺点
- 函数执行完之后,函数内的局部变量没有释放,占用内存时间会变长
- 容易造成内存泄露
2、解决
- 能不用闭包就不用
- 及时释放
function fn1(){
var arr = new Array(10000);
function fn2(){
console.log(arr.length);
};
return fn2;
};
var f = fn1();//定义f接收返回的fn2
f();
f = null;//让内部函数成为垃圾对象
内存溢出和内存泄漏
1、内存溢出
- 一种程序运行出现的错误
- 当程序运行需要的内存超过了剩余的内存,就会抛出内存溢出的错误
//内存溢出
var obj = {};
for(var i =0;i<10000;i++){
obj[i] = new Array(1000000);
console.log('--------');
}
2、内存泄漏
- 占用的内存没有及时释放
- 常见的内存泄漏
(1)意外的全局变量
(2)没有及时清理的计时器或回调函数
(3)闭包
//内存泄漏
//(1)意外的全局变量
function fn(){
a = 3;//没用var直接赋值,是全局变量
console.log(a);
};
//(2)启动循环计时器后不清理或者没有及时清理的回调函数
var intervalId = setIntterval(function(){
console.log('---');},1000
)
//需要清理
clearInterval(intervalId);
//(3)闭包的内存泄漏
function fn1(){
var a = 4;
function fn2(){
console.log(++a);
};
return fn2;
};
var f = fn1();
f();
//使用f = null可以清理
闭包面试题
一、面试题1
1、测试题一
var name = 'The window';
var object = {
name:'My object',
getNameFunc : function(){
return function(){
return this.name;
};
}
};
alert(object.getNameFunc()());//输出The window
2、测试题二
var name2 = 'The window';
var object2 = {
name2:'My object',
getNameFunc : function(){
var that = this;//在这里定义的this已经指向了object
return function(){
return that.name2;
};
}
};
alert(object2.getNameFunc()());//输出my object
二、面试题2
对象创建模式
1、方式一:Object构造函数模式
- 套路:先创建空Object对象,再动态添加属性/方法
- 适用场景:起始时不确定对象内部数据
- 问题:语句太多
var p =new Object();
p.name = 'Tom';
p.age = 12;
p.setName = function(name){
this.name = name;
}
2、方式二:对象字面量模式
- 套路:使用{ } 创建对象,同时指定属性/方法
- 适用场景:起始时对象内部数据是确定的
- 问题:如果创建多个对象,有重复代码
var p ={
name :'Tom',
age : 12,
setName : function(name){
this.name = name;
}
};
3、方式三:工厂模式
- 套路:使用工厂函数(返回一个对象的函数称为工厂函数)动态创建对象并返回
- 适用场景:需要创建多个对象
- 问题:对象没有一个具体的类型,都是Object类型
function creatPerson(name,age){
var obj = {
name:name,
age:age,
setName:function(name){
this.name = name;
}
};
return obj;
}
var p1 = creatPerson('TOM',13);
var p2 = creatPerson('bob',15);
function creatStudent(name,price){
var obj = {
name:name,
price:price,
};
return obj;
}
var s1 = creatPerson('张三',13000);
//s1与p1,p2都是对象类型,区别不开
4、方式四:自定义构造函数模式
- 套路:自定义构造函数,通过new创建对象
- 适用场景:需要创建多个类型确定的对象
- 问题:每个对象都有相同的数据,浪费内存
function Person(name,age){
this.name = name;
this.age = age;
this.setName = function(name){
this.name = name;
}
}
var p1 = new Person('TOM',12);
P1.setName('JACK');
console.log(p1.name,p1.age);
console.log(p1 instanceof Person);
var p2 = new Person('ff',23);
console.log(p1,p2);//p2与p1中有相同的数据,浪费内存
function Student(name,price){
this.name = name;
this.price = price;
}
var s = new Student('bob',12000);
console.log(s instanceof Student);
5、方式五:构造函数+原型的组合模式
- 套路:自定义构造函数,属性在函数中初始化,方法添加到原型上
- 适用场景:需要创建多个类型确定的对象
function Person(name,age){
this.name = name;
this.age = age;
}
Person.prototype.setName = function (name){
this.name = name;
}
var p1 = new Person('TOM',12);
var p2 = new Person('ff',23);
console.log(p1,p2);//方法在原型上面