函数的定义和调用
函数的定义方式
1 2 3 4 5 6 7 8 9
| function fn() {}
var fun = function () {};
var f = new Function('a', 'b', 'console.log(a + b)'); f(1, 2);
|
- 第三种当时执行效率低,较少使用
- 所有函数都是 Function 的实例对象
- 函数也属于对象
函数的调用方式
- 普通函数
1 2 3 4 5
| function fn() { console.log('人生的巅峰'); } fn(); fn.call();
|
- 对象的方法
1 2 3 4 5 6
| var o = { sayHi: function () { console.log('人生的巅峰'); }, }; o.sayHi();
|
- 构造函数
1 2 3 4 5
| function Star(name, age) { this.name = name; this.age = age; } new Star('ldh', 18);
|
- 绑定事件函数
1
| btn.onclick = function () {};
|
- 定时器函数
1
| setInterval(function () {}, 1000);
|
- 立即执行函数
1 2 3
| (function () { console.log('Hello World'); })();
|
this
这些 this 的指向,是当我们调用函数的时候确定的,调用方式的不同决定了 this 的不同指向
调用方式 | this 指向 |
---|
普通函数 | window |
构造函数 | 实例对象 |
对象方法 | 该方法所属对象 |
事件绑定方法 | 绑定事件对象 |
定时器函数 | window |
立即执行函数 | window |
改变函数内部 this 指向
JavaScript 为我们提供了一些函数来处理函数内部 this 指向的问题,常用的有 bind()、call()、apply() 三种方法
call()
cal()方法调用一个对象,简单理解为调用函数的方式,但是它可以改变函数的 this 指向
fun.call(this.Arg, arg1, arg2, ...)
- this.Arg:在 fun 函数运行时 this 指向的对象
- arg1,arg2:传递的其他参数
- 返回值就是函数的返回值,因为它就是调用函数
- 因此当我们想改变 this 指向,同时想调用这个函数时,可以使用 call,比如继承
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| var o = { name: 'andy', };
function fn(a, b) { console.log(this); console.log(a + b); } fn.call(o, 1, 2);
function Father(uname, age, sex) { this.uname = uname; this.age = age; this.sex = sex; }
function Son(uname, age, sex) { Father.call(this, uname, age, sex); } var son = new Son('刘德华', 18, '男'); console.log(son);
|
apply()
apply() 方法调用一个函数,简单理解为调用函数的方式,但是它可以改变函数的 this 指向
fun.apply(this.Arg, {argsArray})
- thisArg:在 fun 函数运行时 this 指向的对象
- argsArray:传递的值,必须包含在数组里面
- 返回值就是函数的返回值,因为它就是调用函数
- 因此,apply 主要跟数组有关系,比如使用 Math.max()求数组的最大值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| var o = { name: 'andy', };
function fn(arr) { console.log(this); console.log(arr); } fn.apply(o, ['pink']);
var arr = [1, 66, 3, 99, 4]; var arr1 = ['red', 'pink'];
var max = Math.max.apply(Math, arr); var min = Math.min.apply(Math, arr); console.log(max, min);
|
bind()
bind() 方法不会调用函数,但是能改变函数内部的 this 指向
fun.bind(thisArg, arg1, arg2, ...)
- thisArg:在 fun 函数运行时 this 指向的对象
- arg1,arg2:传递的其他参数
- 返回由指定的 this 值和初始化参数改造的原函数拷贝
- 因此当我们只是想改变 this 指向,并且不想直接调用这个函数时,可以使用 bind
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| var o = { name: 'andy', };
function fn(a, b) { console.log(this); console.log(a + b); } var f = fn.bind(o, 1, 2); f();
var btn1 = document.querySelector('button'); btn1.onclick = function () { this.disabled = true; setTimeout( function () { this.disabled = false; }.bind(this), 3000 ); };
|
call、apply、bind 总结
相同点:都可以改变函数内部的 this 指向
不同点:
- call 和 apply 会调用函数,bind 不会
- call 传递参数 arg1,arg2 形式,apply 必须数组形式
主要应用场景:
- call 经常做继承
- apply 经常跟数组有关系,比如借助于数学对象实现数组的最大值最小值
- bind 不会调用函数,但也可以改变 this 指向,如改变定时器内部的 this 指向
严格模式
JavaScript 除了提供正常模式外,还提供了严格模式(strict mode)。ES5 的严格模式是采用具有限制性 JavaScript 变体的一种方式,即在严格的条件下运行 JS 代码。严格模式在 IE 10 以上版本的浏览器才支持。
严格模式对正常的 JavaScript 语义做了一些更改:
- 消除了 JavaScript 语法的一些不合理、不严谨之处,减少了一些怪异行为
- 消除代码运行的一些不安全之处,保证代码运行的安全
- 提高编译器效率,增加运行速度
- 禁用了在 ECMAScript 的未来版本可能会定义的一些语法,为未来新版本的 JavaScript 做好铺垫。比如一些保留字:class、enum、export、extends、import、super 不能做变量名
开启严格模式
严格模式可以应用到整个脚本或个别函数中,因此在使用时,我们可以将严格模式分为为脚本开启严格模式和为函数开启严格模式两种情况
为脚本开启严格模式
因为'use strict';
加了引号,所以老版本浏览器会把它作为一行普通字符串而忽略
1 2 3 4 5 6 7
| 'use strict';
(function () { 'use strict'; })();
|
为函数开启严格模式
要给某个函数开启严格模式,需要把'use strict';
声明放在函数体所有语句之前
1 2 3 4 5 6 7 8
| function fn() { 'use strict'; }
function fun() { }
|
严格模式中的变化
严格模式对 JavaScript 的语法和行为都做出了一些改变
变量规定
- 在正常模式中,一个变量没有声明就复制,默认是全局变量。而严格模式禁止这种用法,变量必须先声明再使用
- 严禁删除已经声明的变量,如:delete x; 语法是错误的
this 指向问题
- 以前在全局作用域函数中的 this 指向 window 对象
- 严格模式下全局作用域中函数的 this 是 undefined
- 以前构造函数不加 new 也可以调用,当普通函数,this 指向全局对象
- 严格模式下,如果构造函数不加 new 调用,this 指向的是 undefine,给他赋值会报错
- new 实例化的构造函数指向创建的实例对象
- 定时器中的 this 还是指向 window
- 事件、对象还是指向调用者
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| function fn() { console.log(this); } fn();
function Star() { this.sex = '男'; } Star(); var ldh = new Star(); console.log(ldh.sex);
setTimeout(function () { console.log(this); }, 2000);
|
函数变化
- 函数不能有重名的参数
- 函数必须声明在顶层,为了与 ES6 中的“块级作用域”接轨,不允许在非函数的代码块内声明函数
高阶函数
函数也是一种数据类型,同样可以作为参数,传递给另外一个参数使用,最典型的就是作为回调函数
高阶函数是对其他函数进行操作的函数,它接收函数作为参数或将函数作为返回值输出
1 2 3 4 5 6 7 8 9 10 11 12
| function fn(a, b, callback) { console.log(a + b); callback && callback(); } fn(1, 2, function () { console.log('我是最后调用的'); });
function fn() { return function () {}; } fn();
|
闭包
变量作用域分为:全局变量和局部变量
- 函数内部可以使用全局变量
- 函数外部不可以使用局部变量
- 当函数执行完毕,其作用域内的局部变量会销毁
什么是闭包
闭包(closure)指有权访问另一个函数作用域中变量的函数,简单理解就是一个作用域可以访问另一个函数内部的局部变量
1 2 3 4 5 6 7 8 9 10
|
function fn() { var num = 10; function fun() { console.log(num); } fun(); } fn();
|
在 chrome 中调试闭包
- 打开浏览器,F12 启动 chrome 调试工具
- 设置断点
- 找到 Scope(作用域) 选项
- 当进入断点调试时,Scope 里面会有两个参数(global 全局作用域、local 局部作用域)
- 当执行到 fun 时,Scope 里面会多一个 closure 参数,这就表明产生了闭包
闭包案例
- 循环注册点击事件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| var lis = document.querySelectorAll('.nav li'); for (var i = 0; i < lis.length; i++) { lis[i].index = i; lis[i].onclick = function () { console.log(this.index); }; }
for (var i = 0; i < lis.length; i++) { (function (i) { lis[i].onclick = function () { console.log(i); }; })(i); }
|
- 循环中的 setTimeout()
1 2 3 4 5 6 7 8 9
| 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); }
|
- 计算打车价格
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
|
var car = (function () { var start = 13; var total = 0; return { price: function (n) { if (n <= 3) { total = start; } else { total = start + (n - 3) * 5; } return total; }, yd: function (flag) { return flag ? total + 10 : total; }, }; })(); console.log(car.price(5)); console.log(car.yd(true));
|
递归
如果一个函数在内部可以调用其本身,那么这个函数就是递归函数,由于递归很容易发生栈溢出(Stack Overflow)错误,所以必须要加退出条件 return
- 求 1 * 2 * 3 ... * n 阶乘
1 2 3 4 5 6 7 8 9
| function fn(n) { if (n == 1) { return 1; } else { return n * fn(n - 1); } } console.log(fn(3)); console.log(fn(4));
|
- 求斐波那契数列
1 2 3 4 5 6 7 8 9 10
|
function fn(n) { if (n <= 2) { return 1; } else { return fn(n - 1) + fn(n - 2); } }
|
- 根据 id 返回对应的数据对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| var data = [ { id: 1, name: '家电', goods: [ { id: 11, gname: '冰箱', goods: [ { id: 111, gname: '海尔', }, { id: 112, gname: '美的', }, ], }, { id: 12, gname: '洗衣机', }, ], }, { id: 2, name: '服饰', }, ];
function getID(json, id) { var o = {}; json.forEach(function (item) { if (item.id == id) { o = item; } else if (item.goods && item.goods.length > 0) { o = getID(item.goods, id); } }); return o; } console.log(getID(data, 1)); console.log(getID(data, 2)); console.log(getID(data, 11)); console.log(getID(data, 12)); console.log(getID(data, 111));
|
深拷贝和浅拷贝
浅拷贝
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| var obj = { id: 1, name: 'andy', msg: { age: 18, }, }; var o = {}; for (var k in obj) { o[k] = obj[k]; } console.log(o); o.msg.age = 20; console.log(obj);
console.log('--------------'); Object.assign(o, obj); console.log(o); o.msg.age = 20; console.log(obj);
|
深拷贝
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| var obj = { id: 1, name: 'andy', msg: { age: 18, }, color: ['pink', 'red'], }; var o = {};
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(o, obj); console.log(o);
var arr = []; console.log(arr instanceof Object); o.msg.age = 20; console.log(obj);
|