在介绍做用域特性以前,咱们先来回顾一下js的执行上下文(详细介绍:https://www.jianshu.com/p/8f19e45fd1f1)
一段<script>或者一个函数以内,都会去生成一个执行环境(execution context,EC)或称之为执行上下文。当一段JS代码执行的时候,JS解释器会经过两个阶段去产生一个EC。
1.建立阶段
o建立变量对象VO
o设置[[Scope]]属性的值: (指向做用域链)
o设置this的值: (指向一个EC,默认undefined)
2.初始化变量对象(即设置变量的值、函数的引用),而后解释/执行代码。
注意事项:
全局:针对一段<script>,它会生成一个全局的执行上下文,在执行以前会先去把“变量定义”和“函数声明”拿出来装在对象VO。
函数:针对一个函数,它会生成一个函数执行上下文,在函数执行以前会先把“变量定义”、“函数声明”、“arguments”拿出来封装在对象VO里边。javascript
先执行变量定义后,变量值默认为undefined;
函数声明后不会当即执行,若是输出一下函数,能看到函数体全部的代码;而须要调用函数后(例如:fn();),函数体中的代码才会执行.
arguments 是JavaScript里的一个内置对象,全部的函数都有属于本身的一个arguments对象,它包括了函所要调用的参数。
函数申明和函数表达式的区别:
function fn(name){} 这是一个函数声明,而var a = function(){}这是函数表达式;
若是是函数表达式的写法,在执行以前先拿出来处理的就是var a;了,这时候a只是一个广泛变量(值为undefined)不是函数,这点要注意。html
做用域指的是变量的适用范围。(详细介绍:https://www.w3cschool.cn/javascript_prototype/y2cjfozt.html)java
js无块级做用域(不过es6中let定义的变量只在代码块中有效,也就有了块级做用域)es6
if (true){ var name = 'zhao'; } console.log(name); //zhao,这里依然能够获取到上面代码块里边的变量。
只有函数和全局做用域数组
var a = 100 function fn(){ var a =200; console.log('fn',a); // fn 200 } console.log('global',a); // global 100,获取不到函数内部的变量。
var a = 100; function fn1(){ var b =200; function fn2(){ var c =300; // 当前做用域没有定义的变量,即“自由变量”,a和b都是 console.log(a); // 100 console.log(b); // 200 console.log(c); // 300 } fn2(); } fn1();
PS:自由变量由于在当前做用域没有定义,因此只能去父级做用域查找.
(注意:父级做用域是在函数“定义”的时候就已经肯定了的,自由变量这种一层层往父级查找的链式结构也就是“做用域链”)闭包
闭包其实是对js做用域链的一种应用形式;主要利用了做用域链从父级函数获取变量的特性,从外部调用父级函数局部变量而且互不污染,或者子函数循环利用父级函数的变量达到某种计算用途。app
闭包特性一:调用函数内部的变量,利用做用域链原理,能获取函数fn1的父级函数的局部变量进行计算。
闭包特性二:让这些变量的值始终保持在内存中,不会再fn1调用后被自动清除,再次执行fn1的时候还能继续上一次的计算。
注意:fn2建立的时候与fn1是相互独立的,其中的变量a也互不影响,比如父亲给每一个孩子都准备了一个新的存钱罐。ide
场景一:函数做为返回值函数
function F1(){ var a =100; // 返回一个函数(函数做为返回值),为了阅读方便也能够先定义一个函数,而后retrun函数名。 return function(){ console.log(a); //自由变量,去父做用域寻找 } // 另外放回函数的形式也不仅是return,一样在这里以事件的形式绑定在Dom上也是同样,再或者调用其余方法传递一个函数出去。 } var f1 = F1(); var a=200; f1(); // 100
场景二:函数做为参数传递this
var b=111; function f1(){ var a =100; console.log(a,b); } function f2(fn){ var a =200; var b=222; fn(); } f2(f1); // 100,111 //而且若是a在F1()没有定义的话,就会报错而不是获取f2中的a,由于它的定义时的父级做用域及之上(即全局做用域)都没有定义a;
应用举例一:setTimeout中的参数传递。
因为直接setTimeout(function(){},200)这么写的话,没办法传参,因此能够用闭包的形式来作。
var Fn=function(num){ var a=10; return function(){ var b=0; a+=num; b+=num; console.log(a,b); } } var fn1=Fn(1); var fn2=Fn(1); //闭包特性一:调用函数内部的变量,利用做用域链原理,能获取函数fn1的父级函数的局部变量a进行计算。 setTimeout(fn1,200); //输出的a=11,b=1; //闭包特性二:让变量a的值始终保持在内存中,不会在fn1调用后被自动清除,再次执行fn1的时候还能继续上一次的计算。 setTimeout(fn1,500); //输出的a=12,b=1; //特性二的注意事项:fn2建立的时候与fn1是相互独立的,对应的父级函数Fn的变量a也互不影响,比如父亲在每一个孩子出生时都准备了一个新的存钱罐,每一个孩子都用本身的。 setTimeout(fn2,800); //输出的a=11,b=1;
应用举例二:建立10个<a>标签,点击的时候弹出来对应的序号。
<body> <script type="text/javascript"> for(var i=0;i<10;i++){ (function(i){ var a=document.createElement('a'); a.innerHTML=i+'<br>'; document.body.appendChild(a); a.addEventListener('click',function(e){ e.preventDefault(); //取消默认事件,指a标签 alert(i); }); })(i); } </script> </body>
核心:this要在执行时才能确认值,定义时没法确认。
var a = { name: 'A', fn: function (){ console.log(this.name); } } a.fn(); // this === a (即便是b.a.fn(),this也是a) a.fn.call({name: 'B'}); // this === {name: 'B'} var fn1 = a.fn; fn1(); // this === window
this几种不一样的运用场景
一、做为构造函数执行:(例如:new Foo(),this指向这个新对象)
二、做为对象属性执行:(this指向对象)
三、做为普通函数执行:(this指向window)
四、call()、apply()、bind():(this指向传入的第一个对象参数,bind只有一个参数)
参考:https://www.w3cschool.cn/jsnote/jsnote-this.html
call,apply、bind都属于Function.prototype的一个方法,他们的做用改变函数的调用对象,它是JavaScript引擎内在实现的,由于属于Function.prototype,因此每一个Function对象实例(就是每一个方法)都有call,apply,bind属性。既然做为方法的属性,那它们的使用就固然是针对方法的了,这几个方法是容易混淆的。
call,apply的用法差很少,只是参数稍微不一样;(apply()接收两个参数,一个是函数运行的做用域(this),另外一个是参数数组。call()方法第一个参数与apply()方法相同,但传递给函数的参数必须列举出来。)
// 以最简单window对象为例 function sum(num1, num2) { return num1 + num2; } console.log(sum.call(window, 10, 10)); //20 console.log(sum.apply(window,[10,10])); //20 这两都至关于window.sum(10,10); // 即语法:foo.call(this, arg1,arg2,arg3) == foo.apply(this, arguments)
而bind的用法有一点差异。(只是传一个参数对象,而后返回一个函数给接受的变量,再另外调用执行。)
window.color = "red"; var o = { color: "blue" }; function sayColor(){ alert(this.color); } var OSayColor = sayColor.bind(o); OSayColor(); //blue