JavaScript-作用域、块级作用域、上下文、实行上下文、作用域链
一、函数
1、函数定义
函数是一段能够重复挪用的代码块。函数能够吸收输入的参数,差别的参数会返回差别的值
2、函数的声明体式格局
主要讲两种:
2.1 用function敕令声明函数
function敕令背面是函数名,函数名背面是一对圆括号,内里是传入函数的参数,函数体放在大括号内里
function print(s) {
console.log(s);
}
2.2 用函数表达式声明函数
把匿名函数赋值给变量
var print = function(s) {
console.log(s);
};
3、函数参数
3.1参数定义
参数:从外部传入函数,支持函数运转的外部数据
3.2参数的通报划定规矩
能够多传、少传参数,被省略的参数就是undefined。通报参数是依据递次来适配的。
function printPersonInfo(name, age, sex){
console.log(name)
console.log(age)
console.log(sex)
}
printPersonInfo(sjz,male)//实际上name为sjz ,age为male,sex为undefined
3.3 arguments 对象
1)用途:arguments 对象能够在函数体内部读取一切参数
2)运用划定规矩:arguments对象包含了函数运转时的一切参数,arguments[0]就是第一个参数,arguments[1]就是第二个参数,以此类推。这个对象只要在函数体内部,才能够运用。
3)arugments对象长如许(下图是我通报了三个值)
3)经由过程arguments对象的length属性,能够推断函数挪用时究竟带几个参数
4) 举个例子
function printPersonInfo(name, age, sex){
console.log(name)
console.log(age)
console.log(sex)
console.log(arguments)
console.log(arguments[0])
console.log(arguments.length)
console.log(arguments[1] === age)
}
4、返回值
4.1 用return完成返回函数的操纵后的数值,不写return语句,函数默许返回undefined
4.2 JavaScript 引擎碰到return语句,就直接返回return背面的谁人表达式的值,背面纵然另有语句,也不会获得实行。
4.3返回值的运用
函数能够挪用自身,这就是递归(recursion)。下面就是经由过程递归,盘算斐波那契数列的代码。
function fib(num) {
if (num === 0) return 0;
if (num === 1) return 1;
return fib(num - 2) + fib(num - 1);
}
fib(6) // 8
5、函数声明前置
和变量的声明会前置一样,函数声明同样会前置的。分红两种状况:
5.1用function声明的函数
运用function声明的函数全部函数都邑提拔到代码头部。
所以你在声明函数前,挪用了函数,都不会报错的,以下图
sum(3,4)
function sum(a,b){
return a+b; }
//7
5.2用函数表达式声明函数
不会把全部函数提拔,只会把定义的变量提拔到头部。
相当于以下代码。关于sum2就是一个未赋值的变量,为undefined,是不能作为函数实行的,所以报错了
var sum2;
sum2(3,4);
var sum2 =function (a,b){
return a+b ;}
6、马上实行的函数表达式
关于编辑器来讲function (a,b){return a+b ;} 是一个函数声明语句,而不是一个函数范例的值,所以function (a,b){return a+b ;}加上()是会报错的
准确的写法是(function (a,b){return a+b ;})(), ()内部的东西是一个值,加上()代表马上实行,全部语句相当于一个函数范例的值需要马上实行
7、定名争执
当在同一个作用域内定义了名字雷同的变量和要领的话,会依据前置递次发生掩盖
var fn = 3;
function fn(){}
console.log(fn); // 3
相当于
var fn
function fn(){} //掩盖上面的
fn = 3 //从新赋值
console.log(fn)
function fn(fn){
console.log(fn);
var fn = 3;
console.log(fn);
}
fn(10) //10 3
相当于
function fn(){
var fn =arguments[0];
console.log(fn);
var fn = 3;
console.log(fn);
}
fn(10) //10 3
二、函数作用域
1、定义
作用域(scope)指的是变量存在的局限。
2、分类:
在 ES5 的范例中,Javascript 只要两种作用域:
一种是全局作用域,变量在全部顺序中一向存在,一切处所都能够读取;
另一种是函数作用域,变量只在函数内部存在。
3、全局变量和局部变量
函数外部声明的变量就是全局变量(global variable),它能够在函数内部读取。
在函数内部定义的变量,外部没法读取,称为“局部变量”(local variable)
javaScript 言语特有”链式作用域”构造(chain scope),子对象会一级一级地向上寻觅一切父对象的变量。所以,父对象的一切变量,对子对象都是可见的,反之则不建立。
4、作用域划定规矩
- {}不发生一个作用域,定义函数才会发生一个函数作用域
- 函数在实行的过程当中,先从自身内部找变量
- 假如找不到,再从建立当前函数地点的作用域去找, 以此往上
var a = 1
function fn1(){
function fn2(){
console.log(a)
}
function fn3(){
var a = 4
fn2()
}
var a = 2
return fn3
}
var fn = fn1()
fn() //输出2
var a = 1
function fn1(){
function fn3(){
var a = 4
fn2()
}
var a = 2
return fn3
}
function fn2(){
console.log(a)
}
var fn = fn1()
fn() //输出1
三、闭包
1、定义:
函数连同它作用域链上的要找的这个变量,配合组成闭包
2、特性
闭包最大的特性,就是它能够“记着”降生的环境,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。
3、用途
闭包的最大用途有两个
- 能够读取函数内部的变量
- 暂存数据(让这些变量一直保持在内存中,即闭包能够使得它降生环境一向存在)
4、举个栗子
假如没有这个闭包,函数实行后,内里speed变量就会被清算掉。但我们声清楚明了fn这个函数,并把它返回出来赋值给新的变量speedup。因为speedup是全局变量,是一向存在的,故这个fn函数就一向存在,speed变量也不会被清算
function car(){
var speed = 0
function fn(){
speed++
console.log(speed)
}
return fn//主要,假如不return出来,相当于闭包的作用就没有了
}
var speedUp = car()
speedUp() //1
speedUp() //2
5、闭包典范案例
闭包的典范案例是定义一个变量,一个函数,一个return 函数。如上图
看一下这个案例怎样革新
var fnArr = [];
for (var i = 0; i < 10; i ++) {
fnArr[i] = function(){
return i
};
}
console.log( fnArr[3]() ) // 10
道理剖析:for轮回每次实行,都把function(){ return i} 这个函数赋值给fnArr[i],但这个函数不实行。因为fnArr[3] =function(){ return i};故当我们挪用fnArr[3]() 时,相当于function(){ return i};这个函数马上实行,这时刻for轮回已完成,i已变成了10。故输出10
假如要输出3,需要以下革新
var fnArr = []
for (var i = 0; i < 10; i ++) {
(function(i){
fnArr[i] = function(){
return i
}
})(i)
}
console.log( fnArr[3]() ) // 3
var fnArr = []
for (var i = 0; i < 10; i ++) {
fnArr[i] = (function(j){
return function(){
return j
}
})(i)
}
console.log( fnArr[3]() ) // 3
四、作用域链
1、实行上下文
2、运动对象
Ao有两种泉源,1、来自var定义的变量,2、通报的参数
3、scope属性
实行函数需要值得时刻,就从运动对象AO内里找,找不到就从scope内里去找
4、例子1
var x = 10
bar()
function foo(){
console.log(x)
}
function bar(){
var x=30
foo()
}
1)全局上下文:
globalcontext ={
AO:{
x:10
foo:function
bar:function
},
scope:null
}
2)//声明foo函数得过程当中,foo新增scope属性并指向了globalContext的Ao
foo.[[scope]] =globalContext.Ao
//声明bar函数得过程当中,bar新增scope属性并指向了globalContext的Ao
bar.[[scope]] =globalContext.Ao
在实行上下文的声明的函数,这个函数的[[scope]] 就即是globalContext(实行上下文)的Ao
3)当挪用bar的时刻,进入了bar的实行上下文
barcontext ={
AO:{
x:30
},
scope:bar.[[scope]] // globalContext.Ao
}建立bar的过程当中,bar新增scope属性并指向了globalContext的Ao
4)当挪用foo的时刻,进入了foo的实行上下文
foocontext ={
AO:{},
scope:foo.[[scope]] // globalContext.Ao
}
5、例2
var x = 10
function bar(){
var x=30
function foo(){
console.log(x)
}
foo()
}
bar()
1)全局上下文:
globalcontext ={
AO:{
x:10
bar:function
},
scope:null
}
2)//声明bar函数得过程当中,bar新增scope属性并指向了globalContext的Ao
bar.[[scope]] =globalContext.Ao
3)当挪用bar的时刻,进入了bar的实行上下文
barcontext ={
AO:{
x:30
foo:function
},
scope:bar.[[scope]] // globalContext.Ao
}建立foo的过程当中,foo新增scope属性并指向了barcontext的Ao
foo.[[scope]] =balContext.Ao
4)当挪用foo的时刻,进入了foo的实行上下文
foocontext ={
AO:{},
scope:foo.[[scope]] // balContext.Ao
}
所以console.log(x)是30
6、例子3
封装一个 Car 对象
car对象封装4个接口,我们只能经由过程供应接口来操纵数据,不能直接操纵数据。
道理:定义一个car对象,设置其即是一个马上实行的函数表达式 中return出来的内容。
return出来的对象,有四个属性(setSpeed,get,speedUp,speedDown),四个属性离别对应了四个函数(setSpeed,get,speedUp,speedDown)。这四个函数就用于操纵speed的值。这致使car得不到开释,return的变量也没法开释,对应的一切函数都没有方法开释,就天生了一个闭包
var Car = (function(){
var speed = 0;
function set(s){
speed = s
}
function get(){
return speed
}
function speedUp(){
speed++
}
function speedDown(){
speed--
}
return {
setSpeed: setSpeed,
get: get,
speedUp: speedUp,
speedDown: speedDown
}
})()
Car.set(30)
Car.get() //30
Car.speedUp()
Car.get() //31
Car.speedDown()
Car.get() //30
7、例4
以下代码输出若干?怎样一连输出 0,1,2,3,4
for(var i=0; i<5; i++){
setTimeout(function(){
console.log('delayer:' + i )
}, 0)
}
1)道理:我们设置了延时为0的定时器,每次for轮回一次的时刻,就把函数的代码添加到异步行列内里一次。当for轮回5次轮回完以后,最先实行5次的函数,函数实行时去找i的值,这时刻候的i的值已变成5,所以就一连输出5个5
2)革新
for(var i=0; i<5; i++){
(function(i){
setTimeout(function(){
console.log('delayer:' + i )
}, 0)
})(i)
}
道理:经由过程一个马上实行的函数表达式,天生一个闭包。因为for轮回不会发生一个作用域,所以能够不必return。固然用return也能够
for(var i=0; i<5; i++){
setTimeout((function(j){
return function(){
console.log('delayer:' + j )
}
}(i)), 0)
}
8、例5
function makeCounter() {
var count = 0
return function() {
return count++
};
}
var counter = makeCounter()//**相当于把返回的function() {return count++}这个函数赋值counter**
var counter2 = makeCounter();//**然后把第二次返回的function() {return count++}这个函数赋值counter2**
console.log( counter() ) // 0 //**counter() 每实行一次,就会返回一个数值加1的counter值**
console.log( counter() ) // 1
console.log( counter2() ) // 0
console.log( counter2() ) // 1
道理:因为形成了一个闭包 , counter和counter2 返回的函数存的不是同一个地点,所以关于counter和counter2对应的运动对象是不一样的
9、例6写一个 sum 函数,完成以下挪用体式格局
console.log( sum(1)(2) ) // 3
console.log( sum(5)(-1) ) // 4
剖析:sum(1)以后能随着一个(),示意sum(1)是一个还没有实行的函数,即是function sum(){
return function(){}}。
sum(1)背面接了一个(2)示意返回的函数要吸收一个参数,自身也要接收一个参数。function sum(a){
return function(b){}
}
末了依据这个函数的功用,返回a+b的值
function sum(a) {
return function(b) {
return a + b
}
}
总结:函数柯里化-只通报给函数一部分参数来挪用它,让它返回一个函数去处置惩罚剩下的参数。
10、补全代码,完成数组按姓名、年岁、恣意字段排序
var users = [
{ name: "John", age: 20, company: "Baidu" },
{ name: "Pete", age: 18, company: "Alibaba" },
{ name: "Ann", age: 19, company: "Tecent" }
]
users.sort(byName)
users.sort(byAge)
users.sort(byField('company'))
sort背面必需要接收一个函数,所以需要返回一个参数。
function byName(user1, user2){
return user1.name > user2.name
}
function byAge (user1, user2){
return user1.age > user2.age
}
function byFeild(field){
return function(user1, user2){
return user1[field] > user2[field]
}
}
users.sort(byField('company'))