js对象系列【二】深入理解js函数,详解作用域与作用域链。

时间:2022-10-31 10:11:15

这次说一下对象具体的一个实例:函数,以及其对应的作用域与作用域链。简单的东西大家查下API就行了,这里我更多的是分享自己的理解与技巧。对于作用域和作用域链,相信绝大多数朋友看了我的分享都能基本理解,少数人看完之后再努力思考思考,基本也就懂了。最后说一下,不合理的地方,欢迎批评指正。

函数调用

跳过基本的函数定义,直接说函数调用,js中的函数调用有以下四种方式:

1.直接调用

2.作为对象的方法调用

当作为对象调用时,这里的this指向调用方法的对象,而我们所说的链式调用即是在函数内部作用域的最后return this。当函数不需要明确的返回值时,我们常常将this上下文返回,养成这种习惯有助于后期使用链式调用提高工作效率。

3.通过构造函数调用,值得注意的是,如果构造函数没有形参,圆括号是可以省略的,如下:

new Array();
new Array;

构造函数的返回值固定为实例对象,无法修改。

另外,当构造函数作为对象的方法调用时,构造函数中的this仍指向实例对象

        var obj = {
a: function (name, sex) {
this.name = name;
this.sex = sex;
console.log(this)
}
}
var stu = new obj.a('kevin',18) // a {name: "kevin", sex: 18}

4.函数通过call()和apply() 间接调用方法,call和appy的第一个参数为this上下文指向的母对象.

当以对象a的方法来调用fn(x,y)时

fn.call(a, x, y);
fn.apply(a, [x, y]);

当使用 call() 和 apply() 方法时(  以对象a的方法来调用fn() )

fn.call(a)

等价于如下代码:

a.m = fn;
a.m();
delete a.m;

可选形参

当我们在定义函数时,经常需要考虑形参的排列顺序。传入实参的个数可以小于形参,但必须是一一对应,并且可选的形参必须放在形参列表的最后。

使用/*optional*/b 表示b为可选参数

function test (a, /*optional*/b) {

// .....

}

与可选形参相比我们则常用对象形参来代替形参列表,此方法常常应用于编写插件的配置项

 作用域

说道函数,就不得不说作用域,我们在工作中,经常会遇到这样的错误

1. XXX is not defined

2. can't find property 'XXX'

常见的原因就是:调用方法的对象未被找的(未成功获取)或变量在此作用域内未被找到。

而在声明变量时我们则应该声明在函数作用域的最顶端,此习惯可以让开发者对变量所处的作用域一目了然。

js中的作用域为函数作用域,即每个函数内部为一个作用域(作用域也可称为执行环境)。当系统在执行js代码时,会创建一个作用域链,此作用域链的数据结构为类栈(注意不完全等同于栈,这里多说无益)。

每个作用域的代码在执行时都会创建一个变量对象(VO),此变量对象(VO)中包括了在该作用域中定义的所有变量和函数。由外层向内层的变量对象(VO)会被依次push进作用域链中,全局作用域的变量对象(VO)始终会在作用域链的最底端(es5的声明提前所决定),而当前执行代码的作用域对象(VO)始终在作用域链的最顶端。当我们在内部作用域修改全局作用域的变量的值时,由于全局变量对象在栈的最底部,栈的指针需要依次寻找至栈的最底部,并修改全局作用域变量对象的相应值。如果所有变量都定义在全局变量中,内部变量确实是可以访问得到,但是,执行效率会大大降低。这就是我们为什么需要减少不必要的全局声明的原因,只调用一次的变量或者函数,尽可能的在其执行环境所对应的作用域中声明。

执行环境 = {
VO:{/*函数中的arguments对象、参数、内部变量以及函数声明*/}
this:{},
Scope:{/*当前作用域的VO以及所有父执行上下文中的VO(与prototype类似)*/}
}

注意:在当前作用域的代码真正执行时,变量对象的值(变量和函数)才会初始化完成。(下文会举例说明)

在js中每个变量都有其自身的归属,我们可以把用户创建的所有变量类比为一个大家庭中的成员。而全局作用域的变量对象可看做是“祖宗”,而作用域链就可看做是家庭的“血缘”,每个函数作用域则可看作是一辈(一代)家庭成员,每个家庭的目标与关注度都放在当前(最新)一辈人【当前执行环境对应的作用域】,而每个家庭也都不能忘本,都要谨记祖宗或长辈的教诲【父作用域的变量】。

一说变量的作用域链,就离不开一个老生常谈的例子:

// aBtn 为五个按钮的类数组
// 起初,我们都很渴望打印出 0 1 2 3 4
var aBtn = document.querySelectorAll('button');
for (var i=0; i<aBtn.length; i++) {
aBtn[i].onclick = function () {
console.log(i)
}
}
// 事实上,全部都是 5

这就用到上文所说的,for循环中的匿名函数在调用之前,i的值已经全部为5,也就是for循环已经执行完。

从个人理解来讲,不外呼以下四种方法:

        // 1 .
// 为btn增加加自定义属性index,使其在匿名函数中可通过this上下文获取
var aBtn = document.querySelectorAll('button');
for (var i=0; i<aBtn.length; i++) {
aBtn[i].index = i;
aBtn[i].onclick = function () {
console.log(this.index)
}
}
        // 2.
// 在onclik的事件处理函数上级强行增加一个作用域(一代人),并在此作用域内初始化相应的i值
var aBtn = document.querySelectorAll('button');
for (var i=0; i<aBtn.length; i++) {
clickFn(aBtn[i], i);
}
function clickFn (btn, index) {
btn.onclick = function () {
console.log(index)
}
}
        // 同第二种方法类似,只不过函数改为了匿名函数
var aBtn = document.querySelectorAll('button');
for (var i=0; i<aBtn.length; i++) {
(function (i) {
aBtn[i].onclick = function () {
console.log(i)
}
})(i)
}
        // 4.
// 使用es6的let声明变量,则在for循环的{}内也可看做是一个作用域
var aBtn = document.querySelectorAll('button');
for (let i=0; i<aBtn.length; i++) {
aBtn[i].onclick = function () {
console.log(i)
}
}

闭包

闭包时作用域链的特殊应用的产物,特殊就特殊在闭包所指向的作用域与函数在定义时对应的作用域不同

用一句话概括闭包的形式即:函数b嵌套在函数a内部,函数a返回函数b

        function a () {
var x = 0;
function b () {
x++
return x
}
return b()
}
console.log(a()) //

出现闭包的原因是,有时候根据逻辑需要,我们要在父级作用域中使用局部变量,而闭包就恰好解决了这个问题。另一方面使用闭包获得的局部变量不会在局部作用域失效后就被清除。而是被保留下来。这是把双刃剑,而它的缺点就是滥用闭包很容易造成“循环使用”以至于导致内存泄漏。

下面我们看一个特殊的例子:

       function counter () {
var n = 0;
return {
count (num) {
n = n + num;
return n
},
reeset () {
n = 0
}
}
}
var a = counter();
var b = counter ();
console.log(a.count(1)) //
console.log(b.count(2)) //

这个例子就说明每次调用counter()都会出现一个新的作用域链分支和一个新的私有变量n,两个私有变量互不影响。

像上面的特殊闭包,可以使用对象的存取器属性实现:

详细了解Object的属性,可见上一篇文章:http://www.cnblogs.com/pomelott/p/8082951.html

       var obj = {
n: 0,
get count () {
return this.n
},
set count (val) {
this.n = this.n + val;
return this.n
}
}
obj.count = 5;
console.log(obj.n) //
console.log(obj.count) //

函数的其他可挖掘内容和技巧还很多,这期暂时先分享到这。后续请继续关注。

js对象系列【二】深入理解js函数,详解作用域与作用域链。的更多相关文章

  1. Vagrant系列&lpar;二&rpar;----Vagrant的配置文件Vagrantfile详解

    一.简介 在我们的工作目录下有一个Vagrantfile文件,里面包含有大量的配置信息,通过它可以定义虚拟机的各种配置,如网络.内存.主机名等,主要包括三个方面的配置,虚拟机的配置.SSH配置.Vag ...

  2. &lbrack;转&rsqb;javascript console 函数详解 js开发调试的利器

    javascript console 函数详解 js开发调试的利器   分步阅读 Console 是用于显示 JS和 DOM 对象信息的单独窗口.并且向 JS 中注入1个 console 对象,使用该 ...

  3. js实现的新闻列表垂直滚动实现详解

    js实现的新闻列表垂直滚动实现详解:新闻列表垂直滚动效果在大量的网站都有应用,有点自然是不言而喻的,首先由于网页的空间有限,使用滚动代码可以使用最小的空间提供更多的信息量,还有让网页有了动态的效果,更 ...

  4. JavaScript正则表达式详解(二)JavaScript中正则表达式函数详解

    二.JavaScript中正则表达式函数详解(exec, test, match, replace, search, split) 1.使用正则表达式的方法去匹配查找字符串 1.1. exec方法详解 ...

  5. PHP输出缓存ob系列函数详解

    PHP输出缓存ob系列函数详解 ob,输出缓冲区,是output buffering的简称,而不是output cache.ob用对了,是能对速度有一定的帮助,但是盲目的加上ob函数,只会增加CPU额 ...

  6. C&plus;&plus; list容器系列功能函数详解

    C++ list函数详解 首先说下eclipse工具下怎样debug:方法:你先要设置好断点,然后以Debug方式启动你的应用程序,不要用run的方式,当程序运行到你的断点位置时就会停住,也会提示你进 ...

  7. main&period;js index&period;html与app&period;vue三者关系详解

    main.js index.html与app.vue三者关系详解 2019年01月23日 11:12:15 Pecodo 阅读数 186   main.js与index.html是nodejs的项目启 ...

  8. 若依管理系统RuoYi-Vue(二):权限系统设计详解

    若依Vue系统中的权限管理部分的功能都集中在了系统管理菜单模块中,如下图所示.其中权限部分主要涉及到了用户管理.角色管理.菜单管理.部门管理这四个部分. 一.若依Vue系统中的权限分类 根据观察,若依 ...

  9. 【转】angularjs指令中的compile与link函数详解

    这篇文章主要介绍了angularjs指令中的compile与link函数详解,本文同时诉大家complie,pre-link,post-link的用法与区别等内容,需要的朋友可以参考下   通常大家在 ...

  10. angularjs指令中的compile与link函数详解

    这篇文章主要介绍了angularjs指令中的compile与link函数详解,本文同时诉大家complie,pre-link,post-link的用法与区别等内容,需要的朋友可以参考下   通常大家在 ...

随机推荐

  1. Android学习笔记之图片轮播&period;&period;&period;

    PS:一个bug又折腾了一个下午....哎... 学习内容: 1.Android利用ViewPager和PagerAdapter实现图片轮播... 2.使用反射机制获取Android的资源信息... ...

  2. tomcat如何简单调优

    我们在javaEE开发的过程中,经常会进行tomcat调优操作,下面我们来简单讲解一下tomcat调优. 1) 去掉web.xml的监视,提前将jsp编译成servlet. 2)在物理内存允许的范围内 ...

  3. 复杂SQL查询实例-5种普惠产品必须显示&period;&period;&period;

    复杂SQL需求: 1.查询productCode in (1, 2, 4, 5, 7)五种 2.5种产品必须固定显示,优先显示procuct_status='1'在售产品,在售产品卖完则售罄产品顶上来 ...

  4. XSS攻击及预防

    跨站脚本攻击(Cross Site Scripting),为不和层叠样式表(Cascading Style Sheets, CSS)的缩写混淆,故将跨站脚本攻击缩写为XSS.恶意攻击者往Web页面里插 ...

  5. redhat6&period;5 redis单节点多实例3A集群搭建

    在进行搭建redis3M 集群之前,首先要明白如何在单节点上完成redis的搭建. 单节点单实例搭建可以参看这个网:https://www.cnblogs.com/butterflies/p/9628 ...

  6. Stack &lbrack;NOIP模拟&rsqb; &lbrack;组合数学经典&rsqb;

    Description栈是常用的一种数据结构,有n个元素在栈顶端一侧等待进栈,栈顶端另一侧是出栈序列.你已经知道栈的操作有两种: push 和 pop ,前者是将一个元素进栈,后者是将栈顶元素弹出.现 ...

  7. MyEclipse WebSphere开发教程:安装和更新WebSphere 6&period;1&comma; JAX-WS&comma; EJB 3&period;0(一)

    你开学,我放价!MyEclipse线上狂欢继续!火热开启中>> [MyEclipse最新版下载] MyEclipse支持Java EE技术(如JAX-WS和EJB 3.0),它们以功能包的 ...

  8. 4&period; python 修改字符串实例总结

    4. python 修改字符串实例总结 我们知道python里面字符串是不可原处直接修改的,为了是原来的字符串修改过来,我们有一下方法: 1.分片和合并 >>> a='abcde'  ...

  9. &lbrack;CF1137&rsqb;Museums Tour

    link \(\text{Description:}\) 一个国家有 \(n\) 个城市,\(m\) 条有向道路组成.在这个国家一个星期有 \(d\) 天,每个城市有一个博物馆. 有个旅行团在城市 \ ...

  10. 《JavaScript》web客户端存储

    Web存储: 兼容IE8在内的所有主流浏览器,不兼容早期浏览器:支持大容量但非无限量 LocalStorage和sessionStorage是window对象的两个属性,这两个属性都代表同一个stor ...