本篇为 JavaScript 进阶 ES6 系列笔记第三篇,将陆续更新后续内容。参考:JavaScript 进阶面向对象 ES6 ;ECMAScript 6 入门

系列笔记:

JavaScript 面向对象编程(一) —— 面向对象基础

JavaScript 面向对象编程(二) —— 构造函数 / 原型 / 继承 / ES5 新增方法

「一」函数的定义和调用


1. 函数的定义方式

  1. 函数声明方式 function 关键字(命名函数)
  2. 函数表达式(匿名函数)
  3. new Function()
  • new Function()
new Function ([arg1[, arg2[, ...argN]],] functionBody)
  1. arg1, arg2, ... argN:被函数使用的参数的名称必须是合法命名的。参数名称是一个有效的 JavaScript 标识符的字符串,或者一个用逗号分隔的有效字符串的列表
  2. functionBody:一个含有包括函数定义的 JavaScript 语句的字符串

这种方式执行效率低,不方便书写,较少使用。但是,通过此方式可以知道,所有函数都是 Function 的实例对象,即函数也属于对象。

2. 函数调用方式


此前学习了六种函数,它们分别是:普通函数、对象的方法、构造函数、绑定事件函数、定时器函数、立即执行函数。具体调用方法如下:

// 1. 普通函数
function fn() {console.log('普通函数');
}
fn();       // fn.call()// 2. 对象的方法
var o = {sayHi: function () {console.log('对象方法');}
}
o.sayHi();// 3. 构造函数
function Star() { };
new Star();                 // 4. 绑定事件函数
btn.onclick = function () { };       // 点击调用// 5. 定时器函数
setInterval(function () { }, 1000);   // 每隔 1 秒调用// 6. 立即执行函数
(function () { })();        // 自动调用

「二」函数内部 this 指向


this 的指向是当我们调用函数的时候才被确定的,不同调用方式决定了 this 的不同指向。一般情况下,this 指向函数调用者。

改变函数内部 this 指向


JavaScript 为我们提供了一些函数方法来帮我们更优雅地处理内部 this 的指向问题,常用的有 bind()call()apply() 三种方法。

  • call 方法

call() 方法调用一个对象。可以简单理解为调用函数的方式,但是它可以改变函数的 this 指向。

function.call(thisArg, arg1, arg2, ...)
  1. thisArg:可选的,指 function 函数运行时使用的 this
  2. arg1, arg2, ...:指定的参数列表
        var o = {name: 'andy'}function fn() {console.log(this);};fn.call();      // Windowfn.call(o);     // Object
  • apply 方法

apply() 方法调用一个具有给定this值的函数,以及以一个数组(或类数组对象)的形式提供的参数。

function.apply(thisArg, [argsArray])
  1. thisArg:在 function 函数运行时使用的 this 值。请注意,this可能不是该方法看到的实际值:如果这个函数处于非严格模式下,则指定为 null 或 undefined 时会自动替换为指向全局对象,原始值会被包装。
  2. argsArray:可选,传递的值,必须包含在 数组 里面
  3. 返回值就是函数的返回值,因为它就是调用函数
        var o = {name: 'andy'}function fn(arr) {console.log(this);console.log(arr);}fn.apply(o, ['pink']);  // Object pink// apply 应用 var arr = [1, 3, 2, 6, 5];var max = Math.max.apply(Math, arr);var min = Math.min.apply(Math, arr);console.log(max, min);   // 6 1
  • bind 方法

bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。

function.bind(thisArg, arg1, arg2, ...)
  1. thisArg:调用绑定函数时作为 this 参数传递给目标函数的值
  2. arg1, arg2, ...:传递的其他参数
  3. 返回由指定的 this 值和初始化参数改造的 原函数拷贝
        var o = {name: 'andy'};function fn() {console.log(this);}var f = fn.bind(o);     // 不会调用原函数f();    // Object

实际开发价值:如果有些函数我们不需要立即调用,但是又想改变这个函数内部的 this 指向,此时使用 bind() 是最方便的。

  • 案例:3 秒后恢复点击

<button>点击</button>
<button>点击</button>
<button>点击</button>
<script>var btns = document.querySelectorAll('button');for (var i = 0; i < btns.length; i++) {btns[i].onclick = function () {this.disabled = true;// var that = this;  代替之前所使用的 thatsetTimeout(function () {this.disabled = false;}.bind(this), 2000);   // bind 在定时器函数外面,this 指向 btn 对象}}
</script>

「三」严格模式


本节只是列举了部分常用的严格模式规范,更多可参考 MDN —— 严格模式

JavaScript 除了提供正常模式外,还提供了 严格模式(strict mode)。ES5 的严格模式是采用具有限制性 JavaScript 变体的一种方式,即在严格的条件下执行 JS 代码。

严格模式在 IE10 以上版本的浏览器中才会被支持,旧版本浏览器中会被忽略。

严格模式对正常的 JavaScript 语义做了一些更改:

  1. 消除了 JavaScript 语法的一些不合理、不严谨之处,减少了一些怪异行为
  2. 消除代码运行的一些不安全之处,保证代码运行的安全
  3. 提高编译器效率,增加运行速度
  4. 禁用了在 ECMAScript 的未来版本中可能会定义的一些语法,为未来新版本的 JavaScript 做好铺垫。比如一些保留字如:classenumexportimportsuper 不能做变量名

1. 开启严格模式


严格模式可以应用到整个脚本或个别函数中。因此在使用时,我们可以将严格模式分为 为脚本开启严格模式 和 为函数开启严格模式 两种情况。

  • 为脚本开启严格模式

为整个脚本文件开启严格模式,需要在所有语句之前放一个特定语句 "use strict" (或 'use strict')

    <!-- 为整个脚本(script 标签)开启严格模式 --><script>'use strict';   // 以下 JS 代码按严格模式来执行</script>

有的 script 脚本是严格模式,有的 script 脚本是正常模式,这样不利于文件合并,所以可以将整个脚本文件放在一个立即执行的匿名函数之中。这样独立创建一个作用域而不影响其他 script 脚本文件。

    <script>// 开启独立的作用域空间,防止变量污染(function () {'use strict';})();</script>
  • 为函数开启严格模式

要给某个函数开启严格模式,需要把 "use strict" (或 'use strict')声明放在函数体所有语句之前。

    <script>function fn() {'use strict';// 下面代码按严格模式执行}function fun() {// 仍按照普通模式执行}</script>

2. 严格模式的规范


严格模式对 JavaScript 的语法和行为,都做出了一些改变。

  • 变量规定
  1. 在正常模式中,如果一个变量没有声明就赋值,默认是全局变量。严格模式禁止这种用法,变量都必须先用 var 命令声明,然后才能使用
  2. 严禁删除已经声明的变量
  • 严格模式下 this 指向
  1. 以前在全局作用域函数中的 this 指向 window 对象。而在严格模式下,全局作用域中函数中的 thisundefined
  2. 以前构造函数时不加 new 也可以调用,可以当作普通函数调用,this 指向全局对象。但严格模式下,如果构造函数不加 new 就调用,会报错
  3. new 实例化的构造函数指向创建的对象实例
  4. 定时器的 this 还是指向 Window
  5. 事件、对象还是指向其调用者
  • 函数规范
  1. 函数不能有重名的 参数
  2. 函数必须声明在顶层。新版本的 JavaScript 会引入 “块级作用域” (ES6 中已经引入)。为了与新版本接轨,不允许在非函数的代码块内声明函数
 "use strict";if (true) {function f() { } // !!! 语法错误f();}for (var i = 0; i < 5; i++) {function f2() { } // !!! 语法错误f2();}function baz() { // 合法function eit() { } // 同样合法}

「四」高阶函数


高阶函数是对其他函数进行操作的函数,它 接收函数作为参数 或 将函数作为返回值输出。

下面是 fn 为高阶函数的两种情况:

    <script>function fn(callback) {callback && callback();}fn(function () { });</script>
    <script>function fn() {return function () { }}fn();</script>
  • 举个例子

利用高阶函数,从外部获取异步方法中数据的示例,如下代码:

    function getData() {setTimeout(function () {var name = '张三';return name;}, 1000);}console.log(getData());     // undefined

可以看出,如果直接打印 getData() 是无法得到想要的数据 name 的。这是因为 setTimeout() 是异步方法,其回调函数会在同步任务执行后才会执行,也就是说先执行 console.log(getData()); 因此打印结果为 undefined

可以利用高阶函数,让回调函数作为参数来解决此问题。如下代码:

    function getData(callback) {setTimeout(function () {var name = '张三';callback(name);}, 1000);}getData(function (a) {console.log(a);     // 张三})

相当于

 // 将函数传给 callbackvar callback = function (a) {console.log(a);}// 调用时,name 赋值给了 a callback(name);

此处简单介绍一下,后文还会再讲解。

「五」闭包


1. 变量作用域

变量根据作用域的不同分为两种:全局变量和局部变量。

  1. 函数内部可以使用全局变量
  2. 函数外部不可以使用局部变量
  3. 当函数执行完毕,本作用域内的局部变量会被销毁

2. 闭包概念

闭包(closure)指有权访问另一个函数作用域中变量的 函数 。由于在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成 " 定义在一个函数内部的函数 " 。在本质上,闭包是将函数内部和函数外部连接起来的桥梁。

   function f1() {var n = 999;function f2() {console.log(n);}return f2;}var result = f1();result(); // 999

上述代码中的 f2() 函数,就是闭包。它是典型的高阶函数,实现了从外部读取局部变量。

3. 闭包作用

参考 学习 Javascript 闭包(Closure)

闭包可以用在许多地方。它的最大用处有两个,一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中,进而延伸了变量的作用范围。

  function f1 () {var n = 999;nAdd = function () {n += 1;}function f2() {console.log(n);} return f2;}var result = f1();result();     // 999nAdd();result();  // 1000

在这段代码中,result() 实际上就是闭包 f2 函数。它一共运行了两次,第一次的值是 999,第二次的值是 1000。这证明了,函数 f1 中的局部变量 n 一直保存在内存中,并没有在 f1() 调用后被自动清除。

为什么会这样呢?原因就在于 f1f2 的父函数,而 f2 被赋给了一个全局变量,这导致 f2 始终在内存中,而 f2 的存在依赖于 f1 ,因此 f1 也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。

这段代码中另一个值得注意的地方,就是 nAdd = function() { n += 1 } 这一行,首先在 nAdd 前面没有使用 var 关键字,因此 nAdd 是一个全局变量,而不是局部变量。其次,nAdd 的值是一个匿名函数(anonymous function),而这个匿名函数本身也是一个闭包,所以 nAdd 相当于是一个 setter,可以在函数外部对函数内部的局部变量进行操作。

4. 闭包应用

  • 案例:动态打印 li 标签索引号
    <ul class="nav"><li>海绵宝宝</li><li>派大星</li><li>章鱼哥</li><li>蟹老板</li></ul><script>var lis = document.querySelector('.nav').querySelectorAll('li');// 1. 动态添加属性方式获得索引for (var i = 0; i < lis.length; i++) {lis[i].index = i;lis[i].onclick = function () {console.log(this.index);}}// 2. 利用闭包的方式得到索引for (var i = 0; i < lis.length; i++) {(function (i) {lis[i].onclick = function () {console.log(i);}})(i);}</script>

  • 案例:3 秒后打印各元素内容
    <ul class="nav"><li>海绵宝宝</li><li>派大星</li><li>章鱼哥</li><li>蟹老板</li></ul><script>var lis = document.querySelector('.nav').querySelectorAll('li');for (var i = 0; i < lis.length; i++) {(function (i) {setTimeout(function () {console.log(lis[i].innerHTML);}, 3000)})(i);}</script>

上述两例中绑定点击事件、定时器都属于异步任务,异步任务只有当被触发时才会被推入任务队列依次执行。因此,利用了立即执行函数将对应索引传入。

  • 案例:计程车价格
    var taxi = (function () {var start = 13;     // 起步价 13var total = 0;      // 总价return {price: function (n) {total = total < 3 ? start : (start + (n - 3) * 5);return total;},extra: function (flag) {total = flag ? total + 10 : total;return total;}}})();console.log(taxi.price(1));     // 13console.log(taxi.extra(false)); // 13console.log(taxi.price(5));     // 23console.log(taxi.extra(true));  // 33
  • 思考题

下面看两道思考题来理解闭包的运行机制。

代码一

    var name = "The Window";var object = {name: "My Object",getNameFunc: function () {return function () {return this.name;   // this 指向 Window};}};console.log(object.getNameFunc()());  // The Window

代码二

    var name = "The Window";var object = {name: "My Object",getNameFunc: function () {var that = this;return function () {return that.name;   // this 指向 object};}};console.log(object.getNameFunc()());    // My Object

5. 闭包缺陷

  • 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除(如将变量赋值为 null)。

  • 闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。

「六」递归


如果一个函数在内部可以调用本身,那么这个函数就是 递归函数 。

如下代码,用递归求 n 的阶乘:

    function fn(n) {if (n == 1)return 1;return n * fn(n - 1);}
  • 案例:利用递归遍历数据
    var data = [{id: 1,name: '家电',goods: [{id: 11,gname: '冰箱',goods: [{id: 111,gname: 'Hair'}, {id: 112,gname: 'Media'}]}, {id: 12,gname: '洗衣机'}]}, {id: 2,name: '服饰'}]// forEach 遍历function getData(json, id) {var o = {};json.forEach(function (item) {// 遍历外层if (item.id == id) {// console.log(item);o = item;}// 遍历外层 else if (item.goods && item.goods.length > 0) {o = getData(item.goods, id);}});return o;}console.log(getData(data, 1));      // {id: 1, name: '家电', goods: Array(2)}console.log(getData(data, 11));     // {id: 11, gname: '冰箱', goods: Array(2)}console.log(getData(data, 111));    // {id: 111, gname: 'Hair'}

「七」浅拷贝和深拷贝


1. 直接赋值

谈到拷贝,其实就是将对象复制一份给另一个对象,如下所示代码为将一个对象直接赋值给另一个对象:

    var obj = {id: 1,name: 'andy'};var clone = obj;  // 直接赋值,将对所有的对象属性方法进行浅拷贝obj.id = 2;console.log(clone.id);  // 2

可以发现,尽管只将 obj 中的 id 属性进行修改了,但是 clone 中的 id 属性也发生了变化。这是因为,当创建 obj 对象时,它在堆内存中开辟了一块空间存储对象的内容。而当 clone 直接赋值为 obj 时,clone 并不会再重新开辟一块堆内存,而是将这块内存空间存储的对象的地址给 clone

2. 浅拷贝


与直接赋值的方式不同,浅拷贝是 只拷贝一层,更深层次对象级别的只拷贝引用 。

    var obj = {id: 1,name: 'andy',msg: {age: 18}};var clone = {};for (var k in obj) {// k 是属性名   obj[k] 属性值clone[k] = obj[k];}obj.id = 2;obj.msg.age = 20;console.log(obj.id, obj.msg.age);       // 2  20console.log(clone.id, clone.msg.age);   // 1  20

注意:与直接赋值 var clone = obj; 不同,此处进行浅拷贝的内容是更深层次的对象 msg: { age: 18 },只拷贝其引用。而单独修改 obj 对象的 idname 并不会影响 clone 对象中相应属性的值。内存中关系如下图所示

实现浅拷贝还可以使用 ES6 新增浅拷贝方法

Object.assign(target, ...sources)
  1. target:目标对象,拷贝给谁
  2. sources:源对象,要拷贝的对象

示例

    Object.assign(clone, obj);

等价于普通写法

    for (var k in obj) {// k 是属性名   obj[k] 属性值clone[k] = obj[k];}

3. 深拷贝


深拷贝就不会像浅拷贝那样只拷贝一层,而是将每一级别的数据都进行拷贝,要真正的做到全部内容都放在自己新开辟的内存里,可以 利用递归思想实现深拷贝 。

      var obj = {id: 1,name: 'andy',msg: {age: 18},color: ['blue', 'orange']};var clone = {}function deepCopy(newobj, oldobj) {for (var k in oldobj) {var item = oldobj[k];// 分别判断数组、对象、简单数据类型if (item instanceof Array) {newobj[k] = [];deepCopy(newobj[k], item)} else if (item instanceof Object) {newobj[k] = {};deepCopy(newobj[k], item);} else {newobj[k] = item;}}}deepCopy(clone, obj);obj.id = 2;obj.msg.age = 20;console.log(obj.id, obj.msg.age);       // 2  20console.log(clone.id, clone.msg.age);   // 1  18

注意:这里有一个小细节,要先判断是否为数组(Array),因为 Array 也属于 Object。如果先判断 Object,则 Array 也被当做 Object 进行处理了。

JavaScript 面向对象编程(三) —— 函数进阶 / 严格模式 / 高阶函数 / 闭包 / 浅拷贝和深拷贝相关推荐

  1. python使用高阶函数实现_18.python高阶函数

    什么是高阶函数:一个函数可以作为参数传给另外一个函数(一个函数可以用来接收另一个函数作为参数),或者一个函数的返回值为另外一个函数(若返回值为该函数本身,则为递归),满足其一则为高阶函数.函数的形参位 ...

  2. Python中匿名函数与内置高阶函数详解

    大家好,从今天起早起Python将持续更新由小甜同学从 初学者的角度 学习Python的笔记,其特点就是全文大多由 新手易理解 的 代码与注释及动态演示 .刚入门的读者千万不要错过! 很多人学习pyt ...

  3. Python编程基础:第五十二节 高阶函数High Order Functions

    第五十二节 高阶函数High Order Functions 前言 实践 前言 高阶函数的使用包含两种情况,一种是将另一个函数作为参数,另一种是返回一个函数. 实践 我们先来讲解第一种情况,一个函数将 ...

  4. python装饰器带参数函数二阶导数公式_【计算机程序的构造和解释】使用函数构建抽象——5. 高阶函数...

    学Python,用RPA 艺赛旗RPA2020.1版本 正在免费下载使用中,欢迎下载使用艺赛旗-RPA机器人免费下载|提供流程自动化解决方案​www.i-search.com.cn 我们已经看到,函数 ...

  5. Python 函数的艺术:高阶函数

    我们已经看到,函数实际上是描述复合操作的抽象,这些操作不依赖于它们的参数值.在square中, >>> def square(x):return x * x 我们不会谈论特定数值的平 ...

  6. 函数【七】高阶函数/内置函数

    python函数式编程 高阶函数:就是把函数当成参数传递的一种函数 1.函数名可以进行赋值: 2.函数名可以作为函数参数,还可以作为函数的返回值: a.函数是第一类对象 b.函数可以被赋值 c.可以被 ...

  7. python四大高阶函数_详谈Python高阶函数与函数装饰器(推荐)

    一.上节回顾 Python2与Python3字符编码问题,不管你是初学者还是已经对Python的项目了如指掌了,都会犯一些编码上面的错误.我在这里简单归纳Python3和Python2各自的区别. 首 ...

  8. python高阶函数心得体会_Python高阶函数使用总结

    Datawhale干货   作者:皮钱超,厦门大学,Datawhale原创作者 本文约2000字,建议阅读6分钟 审稿人:耿远昊,Datawhale成员,华东师范大学,开源教程<Joyful-P ...

  9. 廖雪峰讲python高阶函数求导_高阶函数 - 廖雪峰的Python2.7教程 - 广州尚鹏

    高阶函数英文叫Higher-order function.什么是高阶函数?我们以实际代码为例子,一步一步深入概念. 变量可以指向函数 以Python内置的求绝对值的函数abs()为例,调用该函数用以下 ...

最新文章

  1. Redis Sentinel 服务端实现原理
  2. 番茄花园 Ghost XP SP3 金秋国庆版 2013.AA0
  3. 正则化与L0、L1、L2范数祥解
  4. mysql可以存储标点么_MySQL命名、设计及使用规范--------来自标点符的《MySQL命名、设计及使用规范》...
  5. poj 2031 BuildingaSpaceStation 最小生成树 Prim、Kruskal
  6. 爬虫封号解决 -- Win10系统如何修改网卡的物理地址(MAC)
  7. imx6 linux调试平台,iMX6Q调试篇-Linux程序开发使用gdb调试
  8. linux系统开远程桌面,Linux 系统开启远程桌面的方法
  9. Unity+罗技G29方向盘+Realistic Car Controller 制作简单的模拟驾驶
  10. 公司让微信加人有没有软件_没有人想要使用软件
  11. typora快捷键使用
  12. shawn的博客开通啦
  13. vue+element项目,升级element后带tabs模块的页面。导致页面崩溃白屏问题?
  14. c 截取字符串函数代码实现
  15. C程序设计语言之第1章 导言
  16. 使用计算机求解问题首先要,基于计算机的问题求解,首先要完成的是( )
  17. hyper-v导入硬盘无法启动解决方法
  18. oracle12c中dump控制文件,oracle控制文件转储说明
  19. 串口调试助手版本合集
  20. win10如何手动强制关联某个类型文件的默认程序打开方式

热门文章

  1. html resize 最小,Html5 Canvas resize
  2. 快捷指令_iOS快捷指令中心,太实用啦
  3. cocos2d-x学习资源汇总(持续更新。。。)
  4. 第四届CocoaChina开发者大会官网上线
  5. 图书馆管理系统——超期付款
  6. Redis详解(六)------ RDB 持久化
  7. JavaScript学习五
  8. vue 自定义marquee无缝滚动组件
  9. Url几个常用的函数
  10. 轻松入门CAS系列(1)-轻松看懂企业单点登录的解决方案