目录
函数的定义和调用
函数的定义方式
函数的调用方式
this
改变函数内部 this 指向
call 方法
apply 方法
bind 方法
call apply bind 总结
严格模式
开启严格模式
1. 为脚本开启严格模式
2. 为函数开启严格模式
严格模式中的变化
1. 变量规定
2. 严格模式下 this 指向问题
3. 函数变化
高阶函数
闭包
变量作用域
什么是闭包
在 chrome 中调试闭包
闭包的作用
闭包案例
1. 循环注册点击事件
2. 循环中的 setTimeout()
3. 计算打车价格
思考题
递归
利用递归求数学题
1.求 1 * 2 *3 ... * n 阶乘
2. 求斐波那契数列
3. 根据id返回对应的数据对象
浅拷贝和深拷贝
// 3. 利用 new Function('参数1','参数2','函数体');
var f = new Function('a', 'b', 'console.log(a + b)');
f(1, 2);
console.log(f instanceof Object); // f 属不属于 Object
// 1. 普通函数
function fn() {
console.log('人生巅峰1');
};
fn();
// 2. 对象的方法
var o = {
sayHi: function () {
console.log('人生巅峰2');
}
}
o.sayHi();
// 3. 构造函数
function Star() { };
new Star();
// 4. 绑定事件函数
btn.onclick = function () { }; // 点击了按钮就可以调用
// 5. 定时器函数
setInterval(function () { }, 1000); // 这个函数是定时器自动1秒钟调用一次
// 6. 立即执行函数 是自动调用
(function () {
console.log('立即执行');
})();
// 函数不同的调用方式决定this的指向不同
// 1. 普通函数 this指向window
function fn() {
console.log('人生巅峰1' + this);
};
window.fn();
// 2. 对象的方法 this 指向的是对象 o
var o = {
sayHi: function () {
console.log('人生巅峰2' + this);
}
}
o.sayHi();
// 3. 构造函数 this 指向ldh 实例对象 / 原型对象里面的this指向的也是ldh这个实例对象
function Star() { };
Star.prototype.sing = function () { };
var ldh = new Star();
// 4. 绑定事件函数 this指向的是函数的调用者 btn这个按钮对象
var btn = document.querySelector('button');
btn.onclick = function () {
console.log('绑定事件函数的this:' + this);
}; // 点击了按钮就可以调用
// 5. 定时器函数 this 指向的是window
window.setInterval(function () {
console.log('定时器的this:' + this);
}, 1000); // 这个函数是定时器自动1秒钟调用一次
// 6. 立即执行函数 是自动调用 this 指向的是window
(function () {
console.log('立即执行' + this);
})();
JavaScript 为我们专门提供了一些函数方法来帮我们更优雅的处理函数内部 this 的指向问题,常用的有 bind()、call()、apply() 三种方法。
call() 方法调用一个函数。简单理解为调用函数的方式,但是它可以改变函数的 this 指向。
fun.call(thisArg, arg1, arg2, ...)
// 1. call()
var o = {
name: 'andy'
}
function fn(a, b) {
console.log(this);
console.log(a + b);
}
fn.call(o, 1, 2);
// call 可以调用函数,改变函数内的this指向
// call 的主要作用可以实现继承
function Father(uname, age, sex) {
this.uname = uname;
this.age = age;
this.sex = sex;
}
function Son(uname, age, sex) {
// 调用Father 这个函数,然后把Father里面的this指向改为Son,然后传递参数
Father.call(this, uname, age, sex);
}
var son = new Son('ldh', 18, '男');
console.log(son); // Son {uname: "ldh", age: 18, sex: "男"}
apply() 方法调用一个函数。简单理解为调用函数的方式,但是它可以改变函数的 this 指向。
fun.apply(thisArg, [argsArray])
// 2. call()
var o = {
name: 'andy'
}
function fn(arr) {
console.log(this);
console.log(arr); // 'pink'
};
fn.apply(o, ['pink']);
// 1. 也是调用函数,可以改变函数内部的this指向
// 2. 但是他的参数必须是数组(伪数组)
// 3. apply主要应用 比如我们可以用apply 借助于数学内置对象求最大值Math.max();
var arr = [1, 66, 4, 32, 52];
// 调用apply的时候,然后this指向不变,把数组作为参数传给Math
var max = Math.max.apply(Math, arr);
console.log(max); // 66
bind() 方法不会调用函数。但是能改变函数内部this 指向
fun.bind(thisArg, arg1, arg2, ...)
// 3. bind()
var o = {
name: 'andy'
}
function fn(a, b) {
console.log(this);
console.log(a, b);
};
var f = fn.bind(o, 1, 2);
f(); // 调用新函数
// 1. 不会调用原来的函数 可以改变原来函数的this指向
// 2. 返回的是原函数改变this之后产生的新函数
// 4. 我们有一个按钮,当我们点击了之后,就禁用这个按钮,3秒钟之后开启这个按钮
var btn = document.querySelector('button');
btn.addEventListener('click', function () {
this.disabled = true; // 这个this指向的是btn这个按钮
setTimeout(function () { // 定时器函数里面的this 指向的是window
this.disabled = false;
}.bind(this), 3000); // 此时定时器里面的this指向了btn
// bind()是在定时器的外面绑定的,所以这个this指的是btn这个对象
})
区别点:
主要应用场景:
严格模式对正常的 JavaScript 语义做了一些更改:
严格模式可以应用到整个脚本或个别函数中。因此在使用时,我们可以将严格模式分为为脚本开启严格模式和为函数开启严格模式两种情况。
为整个脚本文件开启严格模式,需要在所有语句之前放一个特定语句“use strict”;(或‘use strict’;)。
有的 script 基本是严格模式,有的 script 脚本是正常模式,这样不利于文件合并,所以可以将整个脚本文件放在一个立即执行的匿名函数之中。这样独立创建一个作用域而不影响其他 script 脚本文件。
要给某个函数开启严格模式,需要把“use strict”; (或 'use strict'; ) 声明放在函数体所有语句之前。
'use strict';
// 1. 我们的变量名必须先声明再使用
num = 10;
console.log(num); // 报错
'use strict';
// 2. 不能随意删除已经声明好的变量
var num = 10;
console.log(num);
delete num; // 报错
更多严格模式要求参考:
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Strict_mode
高阶函数是对其他函数进行操作的函数,它接收函数作为参数或将函数作为返回值输出。
此时fn 就是一个高阶函数
函数也是一种数据类型,同样可以作为参数,传递给另外一个参数使用。
最典型的就是作为回调函数。 同理函数也可以作为返回值传递回来
// 高阶函数 - 函数可以作为参数传递
function fn(a, b, callback) {
console.log(a + b);
callback && callback(); // 存在callback && 再执行callback()函数
}
fn(1, 2, function () {
console.log('我是最后调用的');
});
变量根据作用域的不同分为两种:全局变量和局部变量。
1. 函数内部可以使用全局变量。
2. 函数外部不可以使用局部变量。
3. 当函数执行完毕,本作用域内的局部变量会销毁。
闭包(closure)指有权访问另一个函数作用域中变量的函数。 ----- JavaScript 高级程序设计
简单理解就是 ,一个作用域可以访问另外一个函数内部的局部变量,这个局部变量所在的函数就是闭包函数
// 闭包 closure 指有权访问另一个函数作用域中变量 的函数
// 闭包:我们fun这个函数作用域 访问了另外一个函数 fn里面的局部变量 num fn()就是闭包函数
function fn() {
var num = 10;
function fun() {
console.log(num);
}
fun();
}
fn();
1. 打开浏览器,按 F12 键启动 chrome 调试工具。
2. 设置断点。
3. 找到 Scope 选项(Scope 作用域的意思)。
4. 当我们重新刷新页面,会进入断点调试,Scope 里面会有两个参数(global 全局作用域、local 局部作用域)。
5. 当执行到 fun() 时,Scope 里面会多一个 Closure 参数 ,这就表明产生了闭包。
闭包作用:延伸了变量的作用范围。
点击小li,输出小li的索引号
立即执行函数()()
立即执行函数也称为小闭包,因为立即执行函数里面的任何一个函数都可以使用它的i变量
3秒中后,打印所有li元素的内容
- js会先执行同步任务,执行完同步任务后再执行异步任务
- 这里 会先把for循环都执行完成,然后里面的i会变成4 ,再执行定时器里面输出时会报错,所以添加了立即执行函数,循环一次就会执行一次立即执行函数
首先car 等于立即执行函数,然后立即执行函数返回的是price和yongdu 两个函数,我们要知道价格是多少的话,就要调用car里面的price。这个参数传的是公里数
var car = (function () {
var start = 13; // 起步价 局部变量
var total = 0; // 总价 局部变量
return {
// 正常的总价
price: function (n) { // n是公里数
if (n <= 3) {
total = start;
} else {
total = (n - 3) * 5 + start;
}
return total;
},
// 拥堵之后的费用
yongdu: function (flag) {
return flag ? total + 10 : total;
}
}
})(); // 把程序写到立即执行函数里面,不用调用就可以执行了
// 首先car 等于立即执行函数,然后立即执行函数返回的是price和yongdu 两个函数,我们要知道价格是多少的话,就要调用car里面的price。这个参数传的是公里数
console.log(car.price(5)); // 23
console.log(car.yongdu(true)); // 33 拥堵了
console.log(car.price(1)); // 23
console.log(car.yongdu(false)); // 33 没拥堵
// 思考题2:
var name = 'The Window';
var object = {
name: 'My Object',
getNameFunc: function () {
var that = this; // this 指向object ,是object调用的object.getNameFunc()ss
return function () {
return that.name;
}
}
}
// 有闭包的产生,that是局部变量
console.log(object.getNameFunc()()); // My Object
// var f = object.getNameFunc();
// var f = function () {
// return that.name;
// };
// f();
// return that.name;
如果一个函数在内部可以调用其本身,那么这个函数就是递归函数。
简单理解:函数内部自己调用自己, 这个函数就是递归函数
递归函数的作用和循环效果一样 由于递归很容易发生“栈溢出”错误(stack overflow),所以必须要加退出条件 return。
// 递归函数:函数内部自己调用自己,这个函数就是递归函数
var num = 1;
function fn() {
console.log('我要打印六句话');
if (num == 6) {
return; // 递归里面必须加退出条件
}
num++;
fn();
}
fn();
详细思路:
假如用户输入了3,先判断3是否等于1,然后执行 n * fn(n - 1);
return 3 * fn(2)
然后再执行fn(2),2不等于1,return 2 * fn(1)
再执行fn(1),进入if循环,return 1
3 * fn(2)
3 * (2 * fn(1))
3 * (2 * 1)
// 利用递归 求 1 * 2 *3 ... * n 阶乘。
function fn(n) {
if (n == 1) {
return 1;
}
return n * fn(n - 1);
}
console.log(fn(4)); // 24
前两项之和 等于 第三项
我们只需要知道用户输入的n 的前面两项(n-1)(n-2),就可以计算出n对应的序列值
var obj = {
id: 1,
name: 'andy',
msg: {
age: 18
}
};
var o = {};
Object.assign(o, obj); // 浅拷贝(拷贝给谁,拷贝的来源是谁)
console.log(o);
o.msg.age = 20;
console.log(obj); // msg:{age:20}
console.log('--------------');
将复杂数据类型转换成简单数据类型,然后再else里面进行简单数据类型的赋值操作
// 深拷贝拷贝多层,每一级别的数据都会拷贝
var obj = {
id: 1,
name: 'andy',
msg: {
age: 18
},
color: ['pink', 'boue']
};
var o = {};
// 将复杂数据类型转换成简单数据类型,然后再else里面进行简单数据类型的赋值操作
// 判断是创建空的数组和对象
function deepCopy(newobj, oldobj) {
for (var k in oldobj) {// k是属性名 obj[k] 是属性值
// 判断我们的属性值属于哪种数据类型(简单数据类型还是复杂数据类型)
// 1. 获取属性值
var item = oldobj[k];
// 2. 判断这个值是否是数组
if (item instanceof Array) { // 如果这个值是数组
newobj[k] = []; // 给新的obj的属性名弄成数组类型
// 相当于 o.color = [];
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);
o.msg.age = 22;
console.log(obj); // msg:{age:18}