闭包
//实际上js中所有的函数都是闭包
//闭包是由 函数体 和 函数所处的环境(函数作用域) 构成的综合体
//闭包就是函数作用域的应用
闭包的作用:
1.保护变量
2.维持变量的值
闭包的基本模型:
函数嵌套函数,并且让嵌套的函数返回
function fn() {
var num = 123;
// 返回函数的目的:为了在外层函数fn的外部来访问到函数内部的变量num
// 为什么将函数返回以后,还能在函数外部访问到变量num的值???
// 不管函数在哪个地方调用,最终还是要回到创建它的环境中执行
// 因为这个函数是在外部函数fn中创建的,所以,返回函数的作用域链包含了外层函数fn
// 的作用域,所以,能够访问到变量num的值
return function() {
// console.log(num);
console.log(++num);
};
}
var foo = fn();
foo();
foo();
从内存角度分析闭包
函数调用是要占用内存的
比如:函数内部声明的变量是需要内存来进行存储
function(){
var num = 123;
}
fn()
console.log(num);报错 函数内部无法访问到函数内部的变量
闭包的问题
闭包存在内存占用的问题,闭包占用的内存是不会被释放的
所以,如果滥用闭包,会造成内存浪费(泄露)的问题,影响程序的性能
最佳实践:只在必要的情况下使用闭包!
function fn(){
var num = 1;
return function(){
console.log(++num)
}
}
var foo = fn();
foo()
1.调用函数fn(),会占用一块内存
2.这块内存存储了变量num
3.函数fn调用结束,但是调用函数占用的内存没有被释放
4.因为我们将返回的函数赋值给了foo,变量foo现在就是返回函数的引用
5.因为返回函数foo的作用域中包含了 外层函数fn 的作用域,也就是函数foo的作用域链存在对外层函数fn的作用域的引用(占用)
6.因为占用的关系,所以,内存函数foo需要一直都能够访问到外层函数fn里的变量num
7.最终导致的结果就是,外层函数fn虽然调用结束了,但是函数fn的作用域已在内存中得到了维持,也就是函数fn调用的占用的内存没有被释放
JS垃圾回收机制:
var obj = {}; //计数1
var o = obj;//计数2
obj = null; //计数1 手动释放变量占用的内存
o = null;//计数0;
创建对象obj,也就是说,变量obj是对新创建对象的引用
这种情况下,对象{ }占用的内存是不会被回收的,因为变量obj引用了这个对象
obj = null; 手动释放变量占用的内存
将obj的值设置为null,o的值设置为null以后,就没有任何方式能够获取到上面我们创建的对象,那么,这个对象将来就会被JS的垃圾回收机制回收掉;
setTimeout的问题
var num = 1;
setTimeout(function(){
num = 5;
},0);
console.log(num); //1
定时器:
第一个参数:表示一个回调函数
第二参数:表示"延迟时间",必须要经过设置的时间后,才会调用回调函数
注意:调用的这个时间是不准确的!!!
JavaScript单线程的说明
单线程是相对于多线程来说的
所谓的单线程就是:同时只能处理一件事情
与JavaScript密切相关的三个线程:(浏览器提供)
1.++主线程++(执行JS代码,所有的js代码都是由主线程执行)
2.++UI渲染线程++(浏览器将css或js操作DOM的效果渲染到页面中)
3.++事件循环线程++
主线程和UI渲染线程是互斥的,也就是说同时只能有一个线程在运行
如果是主线程在执行(JS代码在执行),那么UI渲染线程就会等待
如果是UI渲染线程在执行(渲染页面效果),那么,主线程就会等待
// 1
console.log('1 开始');
setTimeout(function () {
// 2
console.log('2 setTimeout');
}, 10);
var start = new Date();
for (var i = 0; i < 10000000; i++) {
}
// 3
console.log(new Date() - start);
// 4
console.log('3 结束');
1 主线程会从上往下解析执行JS代码
2 对于第一步,直接执行,打印出 1 开始
3 遇到一个 setTimeout,主线程知道这是一个“异步”(延迟执行)的代码
然后,就将回调函数 function () { console.log('2 setTimeout'); } 放到事件队列中
注意:没有调用回调函数
4 直接打印 3 打印4 结束,到此未知,JS主线程中的代码就执行完毕了
5 此时,JS主线程会通知事件循环线程,让事件循环线程到事件队列中看下有没有正在等待执行的回调函数
6 如果有,事件循环线程就会把这个回调函数取出来交给 主线程
7 最终,这些回调函数,还是由 主线程 来调用
代码执行循序:1>3>4>2
需要被放到事件队列中延迟执行的代码有:
1.setTimeout
2.setInterval
主线程中的代码优先级最高,只有等到主线程中所有的代码都执行完成以后,事件队列中的回调函数才会被执行!!!
为什么要使用缓存???
储存一些已经计算过的结果,方便以后再次使用(避免重复计算)
为什么要使用闭包???
为了保护变量(缓存对象)
因为我们使用缓存,也就是我们完全信任缓存中的数据,因为缓存是需要被保护的
任何人都不能修改缓存中的数据
缓存使用的步骤:
1.先判断缓存中有没有我们需要的数据
2.如果有就直接使用,如果没有就先计算然后将计算的结果存储到缓存
函数的四种调用模式:
1.函数调用模式 this--->window
2.方法调用模式 this--->调用方法的对象
3.构造函数调用模式 this--->指向创建出来的实例对象
4.上下文调用模式 (call/apply)
实际上是根据函数中的this的指向,来区分不同的调用模式
this具有动态性,也就是这个函数以不同的方式来调用,函数中的this会指向不同的对象
分析this的问题
1.分析this属于哪一个函数
2.再看这个函数是以什么模式来调用的
对于this的问题,只看函数是怎么被调用的,而不管函数是怎么来的!!!
// 函数
// function fn() {
// console.log(this);
// }
// // 函数调用模式
// fn();
// var obj = {
// name: 'test',
// // 方法
// say: function() {
// console.log(this);
// }
// };
// // 方法调用模式
// obj.say();
// function Person() {
// this.name = 'jack'
// }
// // 构造函数调用模式
// var p = new Person();
// console.log(p);
函数四种调用模式面试题
1.
var num = 333;
function fn(){
var num = 11;
console.log(this.num)//333 函数调用模式 this.指向window
}
fn()
console.log(num)//333
2.
var age = 38;
var obj = {
age: 18,
getAge: function () {
console.log(this.age);// this 指向 obj
}
};
obj.getAge();//方法调用模式
3.
var age = 38;
var obj = {
age: 18,
getAge: function () {
function foo() {
// this --> window
console.log(this.age); // 38
}
foo(); // 函数调用模式
}
};
obj.getAge();
4.
var age = 38;
var obj = {
age: 18,
getAge: function () {
// this -> window
alert(this.age);
}
};
var f = obj.getAge;
f(); // 函数调用模式
// obj.getAge();//方法调用模式
5.
var length = 10;
function fn() {
console.log(this.length);
}
var obj = {
length: 5,
method: function (fn) {
// 形参 fn 是上面创建的函数fn
fn(); // 10
// arguments是实参,arguments[0] 就是第一个实参
// 这是 方法调用模式,此处是,arguments对象调用的方法
// 所以,方法内部的this指向了 arguments,实际上访问的就是 arguments.length
// 也就是 实参的个数
arguments[0]();
}
};
obj.method(fn, 123,12,15,13);
var obj = {
age: 19,
getAge: function() {
console.log(this.age);
}
};
// 访问对象属性的两种形式:
// 1 点运算符
// 2 []
obj.getAge(); // 方法调用模式
obj['getAge']();// 方法调用模式 */
上下文调用模式_说明 练习
上下文调用模式,也叫做:借用方法模式
上下文,即:环境(哪个对象调用的这个方法)
两个方法:
1:apply()
2:call()
特点:可以指定函数内部中的this指向
1.apply()
语法和参数说明:
apply()
语法:函数.apply(this.Arg,数组或伪数组)
这个方法是由Function.prototype提供的!
apply()方法的参数说明:
第一个参数:用于指定由哪个对象来调用这个方法,也就是改变了函数中的this指向
第二个参数:是一个数组或者伪数组,数组或伪数组中每一项都将作为被调用函数的参数
特点:利用第二个参数为数组,能够将数组中的元素取出来作为被调用方法的参数
例如:Math.max.apply(null,arr);此处就利用了apply方法第二个参数为数组的特点
练习:!!!!!!
function fn(num1, num2, num3) {
console.log(this);
console.log(num1);
console.log(num2, num3);
}
var obj = {
name: '这是对象obj'
};
// 作用:实际上这还是在调用函数fn
fn.apply(obj, [1, 3, 99]);
2.call()
call方法的作用与apply方法的作用相同:
区别:第二参数的形式不一样
对于apply来说,第二个参数是一个数组或者位数
对于call来说,除第一个参数以外,其他参数都将作为被调用方法的参数
两者都具备的特点:借用方法,改变借用方法内部的this的指向
练习:
function fn(name,age){
this.name = name;
this.age = age;
}
function Obj(name,age){
fn.call(this,name,age)
}
var a = new Obj("小明",12);
console.log(a)
-----------------------------------------------------------------------------------------------------------------------------------------------
// 人 构造函数
function Person(name, age, gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
// 学生 构造函数
function Student(name, age, gender, score, id) {
// this.name = name;
// this.age = age;
// this.gender = gender;
// Person(); // 这是函数调用模式,函数内部的this指向window
// this 是谁??? 因为这个函数是以构造函数模式调用的,所以,函数内部的this指向 创建出来的实例对象!
Person.call(this, name, age, gender);
this.score = score;
this.id = id;
}
var stu = new Student('jack', 19, 'male', 100, '10010');
// 老师 构造函数
function Teacher(name, age, gender, salary) {
// 1 当前的 this 是谁??? 因为这个函数是以构造函数模式来调用的,所以,
// this 是 实例对象
// 2 让实例对象借用 Person函数,所以,Person中的this指向了 当前的实例对象
Person.call(this, name, age, gender);
this.salary = salary;
}
var t = new Teacher('小明', 20, 'male', 100000);
// 以上代码可以实现功能,但是 name、age、gender 是一些公共的属性
// 我们可以将其处理之后,实现复用!!!
其它情况中的this指向问题
// 1 setTimeout中的this是谁??? window
// setInterval中的this是谁??? window
setTimeout(function () {
// window
console.log(this);
}, 100);
// 2 事件中的this
document.getElementById('btn').onclick = function() {
// 当前的DOM对象
console.log(this);
};
// 3 原型对象方法中的this 是谁???
function Person() {}
Person.prototype.say = function() {
console.log(this);
};
var p = new Person();
p.say(); // 实例对象
bind()方法
语法和使用说明:
作用:与call和apply方法的作用相似,也可以改变函数内部this的指向,但是用法不同
由函数调用bind方法,bind方法只用一个参数,参数为一个对象,bind方法会返回一个新的函数
这个 新返回函数中的this 就与 bind方法的参数对象 绑定到一起了
语法:var newFn = fn.bind(this.Arg);
1.bind方法会返回一个新的函数返回值(),单数不会调用函数
2.新返回函数中的this与bind方法的参数对象绑定到一起了,也就是说,将来调用新返回的函数,新返回函数中的this就是bind方法的参数了
返回函数newFn和fn的关系:
newFn是函数fn的副本(代码逻辑是完全一样的),只是函数中的this指向不同
经典案例
创建绑定函数
bind() 最简单的用法是创建一个函数,使这个函数不论怎么调用都有同样的 this 值。JavaScript新手经常犯的一个错误是将一个方法从对象中拿出来,然后再调用,希望方法中的 this 是原来的对象。(比如在回调中传入这个方法。)如果不做特殊处理的话,一般会丢失原来的对象。从原来的函数和原来的对象创建一个绑定函数,则能很漂亮地解决这个问题:
this.x = 9;
var module = {
x: 81,
getX: function() { return this.x; }
};
module.getX(); // 返回 81
var retrieveX = module.getX;
retrieveX(); // 返回 9, 在这种情况下,"this"指向全局作用域
// 创建一个新函数,将"this"绑定到module对象
// 新手可能会被全局的x变量和module里的属性x所迷惑
var boundGetX = retrieveX.bind(module);
boundGetX(); // 返回 81