如何理解js中的作用域,闭包,私有变量,this对象概念呢?

就从一道经典的面试题开始吧!

题目:创建10个<a>标签,点击时候弹出相应的序号

先思考一下,再打开看看
     //先思考一下你会怎么写,是不是这样?        可是结果呢,弹出来的都是10,为啥?

     var i,a
for(i=0;i<10;i++){
a=document.createElement('a')
a.innerHTML=i+'<br>'
a.addEventListener('click',function(event){
var event=event||window.event
event.preventDefault()
alert(i)
})
document.body.appendChild(a)
}

这个题目答案最后再说,当然,你就可以直接翻到最后了解,不过我不建议这样!!


涉及到今天的变量、作用域、闭包等等,在变量、作用域知识中已经说了一些,再重新简单的说一下。

执行环境:也称之为执行上下文,两个,一个是全局环境(window),一个是函数环境(每个函数都是一个函数环境)

作用域链:当代码在某一个环境中执行,会创建变量对象的一个作用域链,以保证对执行环境有权访问的所有变量和函数的有序访问。外部环境访问不到内部环境;内部环境通过作用域链可以访问外部环境。

块级作用域:js没有块级作用域,类似循环语句、条件判断语句内作用域都是全局的;只有两个执行环境

变量:var操作符判断是局部变量还是全局变量。

浏览器解析:浏览器解析时,先将所有的变量提升,变量都存为undefined,当执行到哪一行才给予赋值;函数声明一开始就是完整的,而函数表达式跟解析变量是一样的

     console.log(a)         //undefined
var a=100
console.log(a) //100 person("double") //double,20
function person(name){ //声明函数
age=20
console.log(name,age)
var age
}

this:引用执行环境的对象;只有在执行时才能确认,定义时无法确认

        //普通的构造函数;this就是构造函数的实例
function Foo(name){
//var this
this.name=name
//return this
}
var f=new Foo("double") //普通对象;this就是这个对象
var obj={
name:"double",
printName:function(){
console.log(this.name)
}
}
obj.printName() //普通函数 this就是window
function fn(){
console.log(this)
}
fn() //call apply bind来设置this的值
function fn1(name,age){
alert(name)
console.log(this)
}
fn1.call({x:100},'double',20) //this 就是{x:100}这个对象
fn1.apply({x:100},["double",20]) //this 就是{x:100}这个对象 var fn2=function (name,age){
alert(name)
console.log(this)
}.bind('double',20) //this 就是double了
fn2()

闭包

这个概念有些抽象,这样理解:有权访问另一个函数作用域中的变量的函数;也就是说一个函数可以访问另一个函数作用域里的变量

一般两个地方,一个作为返回值,一个作为传递的参数

//闭包

//函数作为返回值
function F1(){
var a=100; //返回一个函数(函数作为返回值)
return function(){
console.log(a)
}
} //f1 得到一个函数
var f1=F1()
var a=200
f1() //a=100 执行返回函数,函数在定义函数的作用域查找变量(而非执行函数的作用域),没有才会往父级作用域查找 //函数作为参数传递 function F1(){
var a=100
return function(){
console.log(a)
}
} var f1=F1() //这是个函数 function F2(fn){
var a=200
fn()
}
F2(f1) //a=100 给函数传参给F2

 以闭包为返回值为例,详细说一下

     //这是一个闭包以返回值的例子
function create(){
var a=100
return function(){
console.log(a)
}
}
var a=200
var result=create() //得到返回函数
result() //执行返回函数 a=100 函数会在定义函数的作用域中查找,然后再在父级中查找 当某个函数被调用的时候,会创建一个执行环境及相应的作用域链,使用arguments和其他参数
来初始化函数的活动对象,但在作用域链中,外部函数的活动对象始终处于第二位, 对于create函数内部的匿名函数,其作用域链被初始化包含三个的活动对象,一个自身的,一个为create函数的,一个是全局环境的
一般来说,函数执行完毕,其活动对象就已经被销毁;
但是对于闭包来说,当create函数执行完毕,函数的活动对象依然不会销毁,因为内部的匿名函数一直引用其中的活动对象

再来看一下这个

     //普通对象 的 一个方法
var person={
name:"double",
getName:function(){
console.log(this.name)
}
}
person.getName() //double //普通对象方法返回的是匿名函数
var person={
name:"double",
getName:function(){
return function(){
console.log(this.name)
}
}
}
var name="single"
person.getName()() //single //普通对象方法返回的是匿名函数
var person={
name:"double",
getName:function(){
var that=this //在定义匿名函数前将this对象赋予给that的变量 this和arguments差不多,也要将其赋值给某个变量
return function(){
console.log(that.name)
}
}
}
var name="single"
person.getName()() //double

ok,了解闭包、作用域链的知识后,回到那道面试题,应该知道其中的原因

    正确的写法
var i
for(i=0;i<10;i++){
(function(i){
var a=document.createElement('a')
a.innerHTML=i+'<br>'
a.addEventListener('click',function(event){
var event=event||window.event
event.preventDefault()
alert(i) //去父作用域查找
})
document.body.appendChild(a)
})(i)
} //js没有块级作用域,所以alert(i)的i是在全局作用域下的,返回的当然是10,
//如何让i是每个弹出来的呢?
//我们知道环境分函数环境和全局环境,只需要将每个i都有自己的执行环境就可以了
//怎么弄?
//直接加个自执行函数,让每个i都有自己的函数环境,也就是构建出私有作用域

内存泄露

由于闭包的存在,外部函数的变量始终被引用着,闭包不结束,则引用始终存在,内存也就不会被回收

       //内存泄露
function person(){
var element=document.getElementById("someElement")
element.onclick=function(){
alert(element.id)
}
} //解决内存泄露
function person(){
var element=document.getElementById("someElement")
var id=element.id //将所需内容赋值给一个变量
element.onclick=function(){
alert(id)
}
element=null //闭包会引用包含函数的整个活动对象,其中包含着element
}

模仿块级作用域

都是知道的,js没有块级作用域,所以要模仿,怎么模仿呢?

上面的面试题已经告诉了:通过匿名函数来构造函数环境,形成私有作用域

下面解释有关涉及到匿名函数,函数声明,函数表达式的东西,说明一下块级作用域

     //变量名只是个值的另一种形式
var count=10
alert(count)
alert(10) //既然变量可以,那么函数呢
var person=function(){
alert("hello")
}
person() var person=function(){
alert("hello")
}() function(){
alert("hello")
}() //报错
//为啥?因为js将function关键字作为一个函数声明的开始,而函数声明是不能加圆括号 (function(){
alert("hello")
})()
//将函数声明转为函数表达式即可,外部加个圆括号即可,也就是形成自己的私有作用域

看了汤姆大叔的博客,关于这个,他是这么说的

     //对于函数声明和函数表达式的转换
//js中括号()是不包含语句的,和js中的&&,||,逗号等操作符是在函数表达式和函数声明上消除歧义的
//所以
var i=function(){console.log("hello")}()
true &&function(){console.log("hello")}()
0,function(){console.log("hello")}()
!function(){console.log("hello")}()
~function(){console.log("hello")}()
-function(){console.log("hello")}()
+function(){console.log("hello")}()
new function(){console.log("hello")}
var value=10
new function(){console.log(value)}(value) //括号传递参数

关于闭包的知识,就说这么多

解析js中作用域、闭包——从一道经典的面试题开始的更多相关文章

  1. 详解js中的闭包

    前言 在js中,闭包是一个很重要又相当不容易完全理解的要点,网上关于讲解闭包的文章非常多,但是并不是非常容易读懂,在这里以<javascript高级程序设计>里面的理论为基础.用拆分的方式 ...

  2. 浅谈JS中的闭包

    浅谈JS中的闭包 在介绍闭包之前,我先介绍点JS的基础知识,下面的基础知识会充分的帮助你理解闭包.那么接下来先看下变量的作用域. 变量的作用域 变量共有两种,一种为全局变量,一种为局部变量.那么全局变 ...

  3. JS中的闭包(closure)

    JS中的闭包(closure) 闭包(closure)是Javascript语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现.下面就是我的学习笔记,对于Javascript初学者应该是很有用 ...

  4. 浅解析js中的对象

    浅解析js中的对象 原文网址:http://www.cnblogs.com/foodoir/p/5971686.html,转载请注明出处. 前面的话: 说到对象,我首先想到的是每到过年过节见长辈的时候 ...

  5. js中的闭包之我理解

    闭包是一个比较抽象的概念,尤其是对js新手来说.书上的解释实在是比较晦涩,对我来说也是一样. 但是他也是js能力提升中无法绕过的一环,几乎每次面试必问的问题,因为在回答的时候.你的答案的深度,对术语的 ...

  6. Js中的闭包原理

    要了解清楚js中的闭包制机,那么得先了解全局执行环境.块级执行环境.函数执行环境.变量对象.环境栈.作用域链.摧毁执行环境. 全局执行环境 全局执行环境指的是最外层的执行环境.在web中全局执行环境被 ...

  7. js中的闭包理解一

    闭包是一个比较抽象的概念,尤其是对js新手来说.书上的解释实在是比较晦涩,对我来说也是一样. 但是他也是js能力提升中无法绕过的一环,几乎每次面试必问的问题,因为在回答的时候.你的答案的深度,对术语的 ...

  8. js中的闭包理解

    闭包是一个比较抽象的概念,尤其是对js新手来说.书上的解释实在是比较晦涩,对我来说也是一样. 但是他也是js能力提升中无法绕过的一环,几乎每次面试必问的问题,因为在回答的时候.你的答案的深度,对术语的 ...

  9. js中的“闭包”

    js中的“闭包” 姓名:闭包 官方概念:闭包是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分. ( ⊙o⊙ )!!!这个也太尼玛官方了撒,作为菜鸟的 ...

随机推荐

  1. AWS上获取监控数据(EC2/RDS都支持)

    方法1:mon-cmd http://docs.aws.amazon.com/zh_cn/AmazonCloudWatch/latest/cli/SetupCLI.html(安装连接) ● Step ...

  2. ESLint 使用入门 - 来自推酷

    在团队协作中,为避免低级 Bug.产出风格统一的代码,会预先制定编码规范.使用 Lint 工具和代码风格检测工具,则可以辅助编码规范执行,有效控制代码质量. 在以前的项目中,我们选择 JSHint 和 ...

  3. wifi入侵思路

    一.得到wifi密码   系统:Kali Linux   工具:Aircrack-ng,EWSA   方法:   1.WEP加密:deauth攻击:得到足够报文直接破解.   2.WPA加密:deau ...

  4. eclipse安装java web插件

    1 查看eclipse版本 找到eclipse的安装目录,找到readme文件,打开其中的html文件,我的是4.6版本的,代号是oxygen 2 安装 打开eclipse,点击help-Instal ...

  5. c语言基础学习04

    =============================================================================涉及到的知识点有:程序的三种结构.条件分支语句 ...

  6. SPI、I2C、UART(转)

    UART与USART(转) UART需要固定的波特率,就是说两位数据的间隔要相等. UART总线是异步串口,一般由波特率产生器(产生的波特率等于传输波特率的16倍).UART接收器.UART发送器组成 ...

  7. [国嵌笔记][027][ARM协处理器访问指令]

    协处理器作用 协处理器用于执行特定的处理任务,如数学协处理器可以执行控制数字处理,以减轻处理器的负担.ARM处理器最多可以支持16个协处理器,其中CP15是最重要的一个协处理器 CP15的作用 CP1 ...

  8. SQL Server 使用问题解答(持续更新中)

    问题一:sql server 2014不允许保存更改,您所做的更改要求删除并重新创建以下表 解答:工具-选项-不勾选组织保存要求重新创建表的更改,如下图确定.

  9. Dora.Interception, 为.NET Core度身打造的AOP框架[4]:演示几个典型应用

    为了帮助大家更深刻地认识Dora.Interception,并更好地将它应用到你的项目中,我们通过如下几个简单的实例来演示几个常见的AOP应用在Dora.Interception下的实现.对于下面演示 ...

  10. Linux 离线安装Rubygems详解

    很多时候我们会发现,真实的生成环境很多都没有外网,只有内网环境,这个时候我们又需要安装RubyGems,则不能提供yum命令进行在线安装了,这个时候我们就需要下载安装包进行离线安装.本文主要简单介绍如 ...