这次说一下对象具体的一个实例:函数,以及其对应的作用域与作用域链。简单的东西大家查下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系列(二)----Vagrant的配置文件Vagrantfile详解

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

  2. [转]javascript console 函数详解 js开发调试的利器

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

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

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

  4. C#二次开发BIMFACE系列61 File Management文件管理服务接口二次开发及实战详解

    系列目录     [已更新最新开发文章,点击查看详细] 在我的博客<C#二次开发BIMFACE系列61 File Management文件管理服务接口二次开发及实战详解>最后列出了 Fil ...

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

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

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

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

  7. C++ list容器系列功能函数详解

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

  8. 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的项目启 ...

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

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

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

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

随机推荐

  1. java编程思想第四版第五章习题

    创建一个类, 它包含一个未初始化的String引用.验证该引用被Java初始化成了null package net.mindview.initialization; public class Test ...

  2. mysql打不开表问题解决方案

    做开发时候某一表怎么也打不开,数据也不多,网上查了按下面这篇文章完美解决,但是要记得用root登录mysql: 记一次MySQL中Waiting for table metadata lock的解决方 ...

  3. "No Python interpreter configured for the project " in Pycharm for python selenium

    自动化测试问题: pyCharm提示python Interpreter没有设置,点击configure Python Interpreter,进入Settings 在 Project Interpr ...

  4. Linux分区和挂载(mount命令的学习)

    当Windows系统出现问题的时候,可能需要重装系统,这个时候我们往往会使用系统盘将系统重新安装在C盘上,其他盘上的文件都没有受到重装系统的影 响,这就是分区的好处之一.同样,在Linux中也需要分区 ...

  5. 错误代码: 1381 You are not using binary logging

    1.错误描述 1 queries executed, 0 success, 1 errors, 0 warnings 查询:show master logs 错误代码: 1381 You are no ...

  6. org.hibernate.engine.jndi.JndiException:Error parsing JNDI name[foo]

    1.错误描述 WARN:HHH00027:Could not bind factory to JNDI org.hibernate.engine.jndi.JndiException:Error pa ...

  7. 如何开发jQuery插件

    一:普及JQuery知识 知识1:用JQuery写插件时,最核心的方法有如下两个: $.extend(object) 可以理解为,为JQuery 类添加一个静态方法. $.fn.extend(obje ...

  8. meta的各种参数

    <!DOCTYPE html> <!-- 使用 HTML5 doctype,不区分大小写 --> <html lang="zh-cmn-Hans"&g ...

  9. String.valueOf(Thread.currentThread().getContextClassLoader().getResource("")) 获取项目的绝对路径(shiro项目中来的八)

    一,上代码 String.valueOf(Thread.currentThread().getContextClassLoader().getResource("")) file: ...

  10. 纯CSS实现二级导航下拉菜单--css的简单应用

    思想:使用css的display属性控制二级下拉菜单的显示与否.当鼠标移动到一级导航菜单的li标签时,显示二级导航菜单的ul标签.由于实现起来比较简单,所以在这里直接给出了参考代码. 1.纯CSS二级 ...