作用域链
- 在JS中函数可以创建作用域;
- 函数中又可以创建函数(可以开辟新的作用域);
- 函数内部的作用域可以访问外部的作用域;
- 如果有多个函数嵌套,那么久会构成一个链式的访问结构,也就是作用域链;
- 注意:函数内部作用域可以访问外部的作用域,但是外部的作用域却无法访问内部的作用域。
作用域注意点
- 在获取值和设置值的时候都是访问变量;
- 并非在函数内部写了变量,这个变量就属于当前函数,而是必须使用var 关键字声明的变量才属于当前函数;
- 函数在声明的时候,里面的代码并不会执行,只有在函数调用时才会执行;
- 声明函数时候的函数名,其实也是一个变量名,可以通过这个变量名来进行设置和赋值;
- 注意:在变量内部使用var 关键字声明一个变量并不会把同名的全局变量覆盖掉。
变量搜索原则
- 在使用变量的时候,首先在当前作用域中查找,如果找到就直接使用;
- 如果没有找到就去上一级作用域中查找,如果找到就直接使用,如果没有找到就重复上面的过程;
- 直到0级作用域。
注意点:在当前作用域中使用var 关键字声明变量并不会覆盖掉上一级作用域中的同名变量。
闭包
闭包:通过某种方式实现的一个封闭的,包裹的对外不公开的结构或空间。
原理:变量的访问原则(即上一级的作用域无法访问下一级的作用域),其实函数本身就是闭包。
实现思路:
- 我们需要能够在函数外部访问函数内部的变量,正常情况无法访问;
- 在函数内部如果新创建函数,那么按照作用域链的原则,这个新创建的内部函数能够访问到函数中的这些变量;
- 我们如果能够操作函数中新创建的函数,那么就能够操作函数中的变量(如访问和设置等);
- 如果要能够操作函数中新创建的函数,那么需要在函数中把新创建的函数返回。
- 调用函数,接收并得到其返回值(是一个函数);
- 调用返回值(函数),通过函数传参的方式来设置函数中的变量;
- 调用返回值(函数),通过在函数内部再次return的方式来访问函数中的变量。
基本模式
在函数内部创建函数(内部函数),在这个内部函数中,可以操作外部函数中的变量;
- 在函数(外部)中创建函数(内部函数),在该函数(内部函数)中操作外部函数中的变量;
- 在外部函数中,把内部函数作为返回值返回;
- 调用外部函数,并接受其返回值(是一个函数);
- 调用接受到的返回值(内部函数),来间接的操作外部函数的变量。
作用
最基本的作用:闭包中的变量更安全,只能通过特定的接口来访问。
说明:1. 获取函数内部的数据操作只能通过指定的接口;
2. 对变量的设置操作会更安全,我们可以在设置数据之前进行校验;
3. 延长变量的生命周期。
进程和线程
进程是指系统中正在运行的一个应用程序。
线程:一个进程中可以有一个或者多个线程,线程是CPU调度中最小的单位,是真正执行任务的。
多线程:一个进程中可能有多条线程,多条线程之间并发执行多个不同的任务。
单线程:一个进程中只有一条线程,即同一时间只能执行一个操作,只能干一件事情。
串行执行:按照顺序一个一个的执行任务;
并发执行:多条线程同时执行任务
javascript是单线程的
JS执行任务:
1.渲染任务;
2.代码的主要任务;
3.事件性任务(定时器任务..)。
- setTimeOut 和闭包执行:
方法一:for循环的同时执行即时函数
for (var i = 0; i < 10; i++) {
(function (j) {
setTimeout(function () {
console.log(j);
},0);
})(i);
}
方法二:for循环的同时计时器的执行保存在队列中,由于传参被保留,for循环完成后,打印值为0~9.
for (var i = 0; i < 10; i++) {
setTimeout((function (j) {
return function () {
console.log(j);
}
})(i),0);
}
- div 事件和闭包
方法一:
var divs = document.getElementsByTagName('div');
for (var i = 0; i < divs.length; i++) {
(function (j) {
divs[j].onclick = function () {
alert('我是第' +(j+1) + '个div')
}
})(i);
}
方法二:
var divs = document.getElementsByTagName('div');
for (var i = 0; i < divs.length; i++) {
divs[i].onclick = (function (j) {
return function () {
alert("我是第"+ (j+1) + "div");
}
})(i);
}
函数的特殊性:本身是对象,且能够提供作用域。
- 函数可以在运行时动态创建,还可以在程序执行过程中创建;
- 函数可以赋值给变量,可以被扩展,甚至是删除;
- 函数可以作为其他函数的参数和返回值;
- 函数可以拥有自己的方法和属性。
函数回调:回调函数(回调),当我们把某个函数作为参数传递给另一个函数的时候,这个函数就称为回调函数
- 函数作为其他函数的参数:
function foo(callBack) {
callBack();
}
function demo() {
console.log('demo');
}
foo(demo);//demo
- 对象的方法作为其他函数的参数
var person = {
name : "zs",
showName :function () {
console.log(this.name);
}
}
function foo(callBack) {
callBack();
}
foo(person.showName)
- 函数作为函数的返回值
function sum() {
var a = 0;
return function () {
a ++;
return a;
}
}
var func = sum();
console.log(func());
console.log(func());
console.log(func());
console.log(func());
惰性函数:
某个函数直到第一次使用的时候才被正确的定义,并且其具有向后惰性,执行更少的工作。
应用场景:函数有一些初始化的准备工作要做,且只需要执行一次的情况。
特点:能够更新自己(函数)的实现。
缺点:1.当重新定义自身的时候,已经添加到原始函数的任何属性都会丢失。2.如果函数被赋值给了其他的变量或者是对象方法,那么在使用变量或者是对象方法调用时仍然会执行旧的函数体。
function foo() {
console.log("foo!");
foo = function () {
console.log("new foo!");
}
}
//函数的调用
//foo(); //foo!
//foo(); //new foo!
问题:1. 添加属性;2.把函数赋值给新的变量;3.以对象的方法调用函数。当惰性函数定义在处理以上三种情况的时候,使用新的变量名调用挥着是时以对象的方法来调用函数,那么该函数在执行的时候并不会更新自身,而是执行旧函数体的内容.
<script>
//01 声明函数foo
function foo() {
console.log("foo!");
foo = function () {
console.log("foo! foo!");
}
}
//02 为foo函数对象添加属性
foo.description = "foo函数的描述信息";
//03 把foo函数赋值给其他的变量
var func = foo;
//04 把foo函数赋值给对象中的方法
var obj = {
showFoo:foo
}
//05 验证并演示输出
func(); //foo!
func(); //foo!
console.log(func.description); //foo函数的描述信息
//总结:01 如果把函数赋值给其他的变量,那么在以其他变量的方式调用时不会更新自身,还是执行旧的函数体
obj.showFoo(); //foo!
obj.showFoo(); //foo!
console.log(obj.showFoo.description); //foo函数的描述信息
//总结:02 如果把函数赋值给对象的方法,那么在以对象方法形式调用时不会更新自身,还是会执行旧的函数体。
foo(); //已经更新过foo函数 foo! foo!
foo(); //已经更新过foo函数 foo! foo!
console.log(foo.description); //undefined
</script>
即时函数
定义:在函数定义之后立即执行该函数。
组成: 1. 使用函数表达式来定义函数(匿名函数,注意不能使用函数声明方式) ;2. 在函数表达式末尾添加一组(),表示立即执行当前函数;3.将整个函数包装在()中,有两种方式。
作用:1. 用来将所有的代码包装到当前的作用域中,并且不会将任何的变量泄露到全局作用域中;2. js中没有代码块作用域,而函数是js中唯一可以创建作用域的;3. 即时函数就是利用了函数创建作用域这一点,来实现对一些需要封装且不允许外部访问的操作。
优点:1. 不会产生全局变量,在即时函数内部定义的所有变量都仅仅只是该函数的局部变量,不会造成全局变量污染问题;2. 具有更好的封装性,外部无法访问到该函数内部的数据。
//第一种写法
(function () {
console.log("即时函数的第一种写法");
}());
//第二种写法
(function () {
console.log("即时函数的第二种写法");
})();
即时对象初始化:
- 结构特征:
1. 提供一个对象,在该对象内部提供一个init初始化方法;
2. 使用()把对象包装起来(让字面量变成表达式);
3. 然后随即调用init方法,完成初始化操作。 - 基本结构:
({}).init(); - 模式优点:
在执行一次性的初始化任务时保护全局的命名空间。