js对象系列【二】深入理解js函数,详解作用域与作用域链。
这次说一下对象具体的一个实例:函数,以及其对应的作用域与作用域链。简单的东西大家查下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函数,详解作用域与作用域链。的更多相关文章
- Vagrant系列(二)----Vagrant的配置文件Vagrantfile详解
一.简介 在我们的工作目录下有一个Vagrantfile文件,里面包含有大量的配置信息,通过它可以定义虚拟机的各种配置,如网络.内存.主机名等,主要包括三个方面的配置,虚拟机的配置.SSH配置.Vag ...
- [转]javascript console 函数详解 js开发调试的利器
javascript console 函数详解 js开发调试的利器 分步阅读 Console 是用于显示 JS和 DOM 对象信息的单独窗口.并且向 JS 中注入1个 console 对象,使用该 ...
- js实现的新闻列表垂直滚动实现详解
js实现的新闻列表垂直滚动实现详解:新闻列表垂直滚动效果在大量的网站都有应用,有点自然是不言而喻的,首先由于网页的空间有限,使用滚动代码可以使用最小的空间提供更多的信息量,还有让网页有了动态的效果,更 ...
- C#二次开发BIMFACE系列61 File Management文件管理服务接口二次开发及实战详解
系列目录 [已更新最新开发文章,点击查看详细] 在我的博客<C#二次开发BIMFACE系列61 File Management文件管理服务接口二次开发及实战详解>最后列出了 Fil ...
- JavaScript正则表达式详解(二)JavaScript中正则表达式函数详解
二.JavaScript中正则表达式函数详解(exec, test, match, replace, search, split) 1.使用正则表达式的方法去匹配查找字符串 1.1. exec方法详解 ...
- PHP输出缓存ob系列函数详解
PHP输出缓存ob系列函数详解 ob,输出缓冲区,是output buffering的简称,而不是output cache.ob用对了,是能对速度有一定的帮助,但是盲目的加上ob函数,只会增加CPU额 ...
- C++ list容器系列功能函数详解
C++ list函数详解 首先说下eclipse工具下怎样debug:方法:你先要设置好断点,然后以Debug方式启动你的应用程序,不要用run的方式,当程序运行到你的断点位置时就会停住,也会提示你进 ...
- main.js index.html与app.vue三者关系详解
main.js index.html与app.vue三者关系详解 2019年01月23日 11:12:15 Pecodo 阅读数 186 main.js与index.html是nodejs的项目启 ...
- 若依管理系统RuoYi-Vue(二):权限系统设计详解
若依Vue系统中的权限管理部分的功能都集中在了系统管理菜单模块中,如下图所示.其中权限部分主要涉及到了用户管理.角色管理.菜单管理.部门管理这四个部分. 一.若依Vue系统中的权限分类 根据观察,若依 ...
- 【转】angularjs指令中的compile与link函数详解
这篇文章主要介绍了angularjs指令中的compile与link函数详解,本文同时诉大家complie,pre-link,post-link的用法与区别等内容,需要的朋友可以参考下 通常大家在 ...
随机推荐
- kolla管理openstack容器
本文以nova-api容器为例,说明kolla如何将nova-api配置文件传入容器,容器如何启动nova-api服务并读取配置文件 注:第一部分比较无趣,二三部分 会有意思一些 1. nova-ap ...
- mysql修改记录
增加一列:alter table bf_agt_dep_acct_sap_sub add column cust_age varchar(10) not null; 改变属性:alter table ...
- hive:排序分析函数
基本排序函数 语法: rank()over([partition by col1] order by col2) dense_rank()over([partition by col1] order ...
- R语言·文本挖掘︱Rwordseg/rJava两包的安装(安到吐血)
每每以为攀得众山小,可.每每又切实来到起点,大牛们,缓缓脚步来俺笔记葩分享一下吧,please~ --------------------------- R语言·文本挖掘︱Rwordseg/rJava ...
- 用DDK开发的9054驱动 .
和S5933比较起来,开发PLX9054比较不幸,可能是第一次开发PCI的缘故吧.因为,很多PCI的例子都是对S5933,就连微软出版的<Programming the Microsoft Wi ...
- Java和Flex整合报错(三)
1.错误描述 信息: Initializing Spring FrameworkServlet 'mvc' 11-13 23:43:42 INFO [localhost-startStop-1] or ...
- Gearman研习笔记(1) ------ 官网介绍要点摘录
之前的项目里使用过消息中间件(公司提供的MQ服务)来做分发,因为MQ是基于消息的,并不是专业的任务分发器,在一些复杂场景上使用起来并不恰当. 后来听组长说了下Gearman(听名字还以为是Ironma ...
- 百度统计&友盟统计
一.百度统计 登录百度站长统计账号-->管理 --->代码获取-->复制代码,如 <script> var _hmt = _hmt || []; (function() ...
- kubernetes Auto Install Guide
1.概念&架构 Kubernetes is an open-source system for automating deployment, scaling, and management o ...
- js获取本机的网络IP地址
JavaScript是一门脚本语言,是不能操作文件,读取本地信息的,所以想要获取IP,还需要借助后端技术.方法如下: //获取本机的网络ip地址 function jsonpCallback(res) ...