1 函数的定义方式
// 函数声明方式 function 关键字 (命名函数)function fn(){}// 函数表达式(匿名函数)var fn = function(){}// new Function()var f = new Function('a', 'b', 'console.log(a + b)');var fn = new Function('参数1','参数2'..., '函数体')
Function 里面参数都必须是字符串格式,函数也属于对象,所有函数都是 Function 的实例(对象)
var a = function (){console.log('test');}console.log(typeof a); // functionconsole.log(a instanceof Function); // trueconsole.log(a instanceof Object); // true
2 函数内 this 的指向
这些 this 的指向,是当我们调用函数的时候确定的。调用方式的不同决定了this 的指向不同
箭头函数的this指向的是函数定义所在对象
3 改变this指向
3.1 call
**call()** 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。
该方法的语法和作用与 [apply()](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/apply) 方法类似,只有一个区别,就是 call() 方法接受的是一个参数列表,而 apply() 方法接受的是一个包含多个参数的数组。
返回值:使用调用者提供的 this 值和参数调用该函数的返回值。若该方法没有返回值,则返回 undefined。
应用场景:经常做继承.
var o = {name: 'andy'}function fn(a, b) {console.log(this);console.log(a+b)};fn(1,2)// 此时的this指向的是window 运行结果为3fn.call(o,1,2)//此时的this指向的是对象o,参数使用逗号隔开,运行结果为3

作继承
// 父类function Person(name, age, sex) {this.name = name;this.age = age;this.sex = sex;}// 子类function Student(name, age, sex, score) {Person.call(this, name, age, sex); // 此时父类的 this 指向子类的 this,同时调用这个函数this.score = score;}var s1 = new Student('zs', 18, '男', 100);console.dir(s1);
3.2 apply方法
调用函数的方式**apply()** 方法调用一个具有给定this值的函数,以及以一个数组(或类数组对象)的形式提供的参数。
// argsArray:传递的值,必须包含在数组里面,返回值就是函数的返回值,因为它就是调用函数fun.apply(thisArg, [argsArray])var o = {name: 'andy'}function fn(a, b) {console.log(this);console.log(a+b)};fn() // 此时的this指向的是window 运行结果为3fn.apply(o,[1,2])//此时的this指向的是对象o,参数使用数组传递 运行结果为3
应用场景: 经常跟数组有关系,比如使用 Math.max() 求数组的最大值
var arr = [1, 66, 3, 99, 4];var max = Math.max.apply(Math, arr);console.log(max); // 99
func.apply(thisArg, [argsArray])
thisArg 必选的。在 _func_ 函数运行时使用的 this 值。
请注意,this可能不是该方法看到的实际值:如果这个函数处于非严格模式下,则指定为 null 或 undefined 时会自动替换为指向全局对象,原始值会被包装。
返回值:调用有指定**this**值和参数的函数的结果
3.3 bind
**bind()** 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。
应用场景: 不调用函数,但是还想改变this指向
var o = {name: 'andy'};function fn(a, b) {console.log(this);console.log(a + b);};var f = fn.bind(o, 1, 2); //此处的f是bind返回的新函数f();//调用新函数 this指向的是对象o 参数使用逗号隔开
案例
var btns = document.querySelectorAll('button')btns.forEach((v, i) => {v.onclick = function () {this.disabled = truesetTimeout((() => {this.disabled = false}).bind(this), 3000)}})
3.4 三者异同
- 共同点 : 都可以改变this指向
- 不同点:
- call 和 apply 会调用函数, 并且改变函数内部this指向.
- call 和 apply传递的参数不一样,call传递参数使用逗号隔开,apply使用数组传递
- bind 不会调用函数, 可以改变函数内部this指向.
应用场景
消除了 Javascript 语法的一些不合理、不严谨之处,减少了一些怪异行为。
- 消除代码运行的一些不安全之处,保证代码运行的安全。
- 提高编译器效率,增加运行速度。
- 禁用了在 ECMAScript 的未来版本中可能会定义的一些语法,为未来新版本的 Javascript 做好铺垫。比如一些保留字如:class,enum,export, extends, import, super 不能做变量名
4.1 开启严格模式
为脚本开启严格模式
<script>"use strict";console.log("这是严格模式。");</script>
为函数开启严格模式
function fn(){"use strict";return "这是严格模式。";}
4.2 严格模式变化
1.变量规定
- 在正常模式中,如果一个变量没有声明就赋值,默认是全局变量。严格模式禁止这种用法,变量都必须先用 var 命令声明,然后再使用。
- 严禁删除已经声明变量。例如,delete x; 语法是错误的
严格模式下 this 指向问题
① 以前在全局作用域函数中的 this 指向 window 对象。
② 严格模式下全局作用域中函数中的 this 是 undefined。
③ 以前构造函数时不加 new也可以调用, 当普通函数,this 指向全局对象
④ 严格模式下,如果构造函数不加new调用, this 指向的是undefined 如果给他赋值则会报错
⑤ new 实例化的构造函数指向创建的对象实例。
⑥ 定时器 this 还是指向 window 。
⑦ 事件、对象还是指向调用者。函数变化
① 函数不能有重名的参数。
② 函数必须声明在顶层. 新版本的 JavaScript 会引入“块级作用域”( ES6 中已引入)。为了与新版本接轨, 不允许在非函数的代码块内声明函数。
5 闭包
闭包(closure)指有权访问另一个函数作用域中变量的函数。 ——- JavaScript 高级程序设计
简单理解就是 ,一个作用域可以访问另外一个函数内部的局部变量。
作用:延伸变量的作用范围。
function fn1(){ // fn1 就是闭包函数var num = 10;function fn2(){console.log(num); // 10}fn2()}fn1();

当执行到 fn2() 时,Scope 里面会多一个 Closure 参数 ,这就表明产生了闭包。
计算打车价格:
/*需求分析打车起步价13(3公里内), 之后每多一公里增加 5块钱. 用户输入公里数就可以计算打车价格如果有拥堵情况,总价格多收取10块钱拥堵费*/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)); // 23console.log(car.yd(true)); // 33
作用域中的局部变量一直被使用着
function getNum(){var n = 0function add() {return n++}return add}var c = getNum()console.log(c()); // 0console.log(c()); // 1console.log(c()); // 2
6 递归
6.1 阶乘
//利用递归函数求1~n的阶乘 1 * 2 * 3 * 4 * ..nfunction fn(n) {if (n == 1) { //结束条件return 1;}return n * fn(n - 1);}console.log(fn(3)); // 6
6.2 利用递归求斐波那契数列
// 利用递归函数求斐波那契数列(兔子序列) 1、1、2、3、5、8、13、21...// 用户输入一个数字 n 就可以求出 这个数字对应的兔子序列值// 我们只需要知道用户输入的n 的前面两项(n-1 n-2)就可以计算出n 对应的序列值function fb(n) {if (n === 1 || n === 2) {return 1;}return fb(n - 1) + fb(n - 2);}console.log(fb(3));
6.3 遍历对象
var data = [{id: 1,name: '家电',goods: [{id: 11,gname: '冰箱',goods: [{id: 111,gname: '海尔'}, {id: 112,gname: '美的'},]}, {id: 12,gname: '洗衣机'}]}, {id: 2,name: '服饰'}];// 我们想要做输入id号,就可以返回的数据对象function getID(json, id) {var o = {};json.forEach(function (item) {if (item.id == id) {o = item;// 2. 我们想要得里层的数据 11 12 可以利用递归函数// 里面应该有goods这个数组并且数组的长度不为 0} else if (item.goods && item.goods.length > 0) {o = getID(item.goods, id);}});return o;}console.log(getID(data, 1));
6.4 自定义深拷贝
深拷贝js库没有提供相应的方法
// 深拷贝拷贝多层, 每一级别的数据都会拷贝.var obj = {id: 1,name: 'andy',msg: {age: 18},color: ['pink', 'red']};var o = {};// 封装函数function deepCopy(newobj, oldobj) {for (var k in oldobj) {// 判断我们的属性值属于那种数据类型// 1. 获取属性值 oldobj[k]var item = oldobj[k];// 2. 判断这个值是否是数组if (item instanceof Array) {newobj[k] = [];deepCopy(newobj[k], item)} else if (item instanceof Object) {// 3. 判断这个值是否是对象newobj[k] = {};deepCopy(newobj[k], item)} else {// 4. 属于简单数据类型newobj[k] = item;}}}deepCopy(o, obj);console.log(o);
还有一种便是先转换成json字符串
// 深拷贝拷贝多层, 每一级别的数据都会拷贝.var obj = {id: 1,name: 'andy',msg: {age: 18},color: ['pink', 'red']};// 先转换成字符串var temp = JSON.stringify(obj)var o = JSON.parse(temp)
