一、函数的定义和调用:
1. 函数的定义方式:
- 自定义函数(命名函数):
function fn() {}
- 函数表达式(匿名函数):
var fn = function() {};
- 利用new:
- 所有函数都是 Function 的实例;
var fn = new Function('参数1', '参数2', ..., '函数体')
// 例
var fn = new Function('a', 'b', 'console.log(a+b);');
fn(1,3);
2. 函数调用:
- 普通函数;
function fn() {
console.log("hello world");
}
fn();
fn.call();
- 对象方法;
var obj = {
fn: function() {
console.log("hello world");
}
}
obj.fn();
- 构造函数;
function Class() {};
var obj = new Class();
- 事件绑定函数;
btn.onclick = function() {}; // 点击按钮
- 定时器函数;
setInterval(function () {
},1000); // 定时器函数, 自定每秒调用一次
- 立即执行函数;
(function() {
console.log("hello world");
})();
二、 this:
1. 函数内this的指向:
这些this的指向, 是当调用函数时确定的. 调用方式的不同决定了this的指向不同;
调用方式 | this指向 |
---|---|
普通函数调用 | window |
构造函数调用 | 示例对象、原型对象里面的方法也是指向实例对象的 |
对象方法调用 | 该方法所属的对象 |
事件绑定函数 | 绑定的事件对象 |
定时器函数 | window |
立即执行函数 | window |
2. 改变函数内部的this指向:
JavaScript提供了一些函数来更优雅的处理函数内部this的指向问题. 常用的有:
2.1call():
- 语法:
fn.call(thisArg, arg1, arg2, ...)
- 可以调用函数;
- 可以改变函数内部的this指向;
var o = {
name: '你好'
}
function fn(a, b) {
console.log(this, a + b);
}
fn.call(o, 2, 3)
// call() 用在对象继承中的作用
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);
2.2 apply():
- 语法:
fun.apply(thisArg, [argsArray]);
- 可以调用函数;
- 可以改变函数内部的this指向;
- 主要应用: 比如借助数学内置对象中的求最大值
fn.apply(o, [2, 3])
var arr = [1,324.342,423,65,876,3]
var _max = Math.max.apply(Math, arr); // 注意严格模式
console.log(_max); // 876
2.3 bind():
- 语法:
func.bind(thisArg, arg1, arg2, ...)
- 返回由指定的this值和初始化参数改造后的原函数拷贝;
- 如果有函数不需要立即调用, 有需要改变函数内部的this指向;
var func_ = fn.bind(o,2,);
func_();
<body>
<button>按钮</button>
<script>
var btn = document.querySelector("button");
btn.onclick = function() {
this.disabled = true;
// 设置定时器, x秒后释放
setTimeout(function() {
this.disabled = false;
}.bind(this), 3000); // bind(this): this指向的事btn
}
</script>
</body>
三、严格模式:
1. 什么是严格模式:
JavaScript除了提供正常模式外, 还提供了严格模式(strict mode); ES5的严格模式是采用具有限制性JavaScript辩题的一种方式, 即在严格的条件下运行JS代码.
严格模式在IE10以上版本的浏览器中才被支持, 旧版浏览器中回忽略;
严格模式对正常模式的JavaScript语法做了一些修改:
- 消除了JavaScript语法的一些不合理, 不严谨之处, 减少了一些怪异行为;
- 消除代码运行的一些不安全之处, 保证代码运行安全;
- 提高编译器效率, 增加运行速度;
- 禁用了ECMAScript的未来版本中可能定义的一些语法, 为未来新版本的JavaScript做好铺垫. 比如一些保留字: class, enum, extend, import, super等, 不能被命名使用;
2. 开启严格模式:
严格模式可以应用到整个脚本或个别函数. 因此,在使用时, 可以将严格模式分为为脚本开启严格模式和为函数开启严格模式
2.1 为脚本开启严格模式:
在整个脚本文件开启严格模式, 需要在所有语句之前放一个特定语句"use strict";
<script>
'use strict';
console.log("这是严格模式");
</script>
<script>
(function() {
"use strict";
console.log("这是严格模式");
})()
</script>
2.2 为函数开启严格模式:
<script>
function fn() {
'use strict';
console.log("只为这个函数开启严格模式");
}
function fun() {
console.log("其他函数为普通模式");
}
</script>
2.3 严格模式中的变化:
- 变量规定:
- 正常模式中, 如果一个变量没有生命就赋值, 默认是全局变量. 严格模式禁止这种方法, 变量都必须先用var声明, 然后再使用;
- 严禁删除声明过的变量; 例如: delete x; 语法是错误的;
- 严格模式下this指向问题:
- 普通模式下, 全局函数中的this指向window对象, 严格模式下全局函数中的this是undefined;
- 严格模式下, 如果构造函数不加new调用, this会报错; new 实例化的结构函数需要指向创建的对象实例;
- 严格模式下, 定时器函数中断this指向的还是window;
- 严格模式下, 事件、对象中的this指向的还是调用者;
- 函数变化:
- 函数不能有重名的参数;
- 函数必须僧名在顶层; 新版本的JavaScript会引入"块级作用域"(ES6中已经引入); 为了与新版本接轨, 不允许在费函数的代码块中声明函数;
- 更多参考: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Strict_mode
四、高阶函数:
高阶函数是对其他函数进行操作的函数; 它接收函数作为参数或将函数作为返回值输出;
// 1. 它接收函数作为参数
function fn(callback) {
callback && callback();
}
fn(function() {
alert('hello');
});
// 2. 将函数作为返回值输出
function fn() {
return function() {}
}
五、闭包:
1. 变量作用域:
变量根据作用域的不同分为两种: 全局变量 和 局部变量;
- 函数内部可以使用全局变量;
- 函数外部不可以使用局部变量;
- 当函数执行完毕, 本作用域内的局部变量会销毁;
2. 什么是闭包:
闭包(closure)值有权访问另一个函数作用域中变量的函数;
简单理解: 一个作用域可以访问另外宇哥函数内部的局部变量;
function fn() {
var num = 10;
function fun() {
console.log(num);
}
fun();
}
fn();
3 .闭包的作用:
作用: 延伸了函数的作用范围
当外函数返回内函数的引用, 则,外部可以访问外函数的局部变量;
function fn() {
var num = 10;
// 外函数返回内函数的引用
return function() {
console.log(num);
}
}
var f = fn();
f();
4. 闭包应用:
4.1 案例一:点击li输出当前li的索引号
<ul class="nav">
<li>苹果</li>
<li>梨</li>
<li>桃</li>
<li>芒果</li>
</ul>
<script>
// 闭包应用-点击li输出当前梨的索引号
var lis = document.querySelector('.nav').querySelectorAll('li');
// 方式一: 使用动态添加属性
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++) {
// 利用循环创建执行函数
// var fun = function(i) {
// lis[i].onclick = function() {
// console.log(i);
// }
// }
// fun(i)
// 优化 -> 立即执行函数也称为"小闭包"
(function(i) {
lis[i].onclick = function() {
console.log(i);
}
})(i)
}
</script>
4.2 案例二: 循环中的setTimeout():
3秒钟后打印所有li元素的内容
<ul class="nav">
<li>苹果</li>
<li>梨</li>
<li>桃</li>
<li>芒果</li>
</ul>
<script>
// 闭包应用 - 3秒钟后打印所有li元素的内容
var lis = document.querySelector('.nav').querySelectorAll('li');
for(var i = 0; i < lis.length; i++){
(function(i) {
setTimeout(function() {
console.log(lis[i]);
}, 3000)
})(i)
}
</script>
4.3 案例三: 计算打车价格:
打车起步价13元(3公里内), 之后每多一公里费用增加5元. 用户输入公里数可以计算打车费用;
如果有拥堵情况, 总费用会多收取10元拥堵费;
var car_cost = (function() {
var start = total = 13;
return {
price: function(n) {
if(n <= 3) {
total = start;
} else {
total = start + (n - 3) * 5
}
return parseInt(total)
},
jam: function(is_jam) {
return is_jam ? (total + 10) : total;
}
}
})()
console.log(car_cost.price(5.6)); // 26
console.log(car_cost.jam(true)); // 36
console.log(car_cost.price(1)); //13
console.log(car_cost.jam(false)); //13
4.4 思考:
- 此时没有闭包:
var name = "the window"
var obj = {
name: "my object",
getNameFunc: function() {
return function() {
return this.name
}
}
}
console.log(obj.getNameFunc()()); // the window
- 此时有闭包:
var name = "the window"
var obj = {
name: "my object",
getNameFunc: function() {
var that = this;
return function() {
return that.name
}
}
}
console.log(obj.getNameFunc()()); // my object
六、递归:
1. 什么是递归:
递归函数: 函数内部调用了自己
由于递归很容易发生"栈溢出"错误(stack overflow), 所以必须要加退出(return)条件;
function fn(n) {
if(n == 0) return
console.log("hello world: " + n);
fn(n-1);
}
fn(6);
// hello world: 6
// hello world: 5
// hello world: 4
// hello world: 3
// hello world: 2
// hello world: 1
2. 案例:
2.1 求阶乘:
function fn(n) {
if(n == 1) return 1
return n * fn(n-1)
}
console.log(fn(6)); // 720 = 1x2x3x4x5x6
2.2 斐波那契数列:
function fb(n) {
if(n == 1 || n == 2) return 1;
return fb(n-1) + fb(n-2)
}
console.log(fb(1)); // 1
console.log(fb(2)); // 1
console.log(fb(3)); // 2
console.log(fb(6)); // 8
2.3 根据id返回对应的数据对象:
var data = [{
id: 1,
name: "家电",
goods: [
{ id: 11, gname: '冰箱' },
{ id: 12, gname: '洗衣机'}]
}, {
id: 2,
name: '服饰'
}]
// 根据输入的id好, 返回对应的数据
function fin(id, cat) {
var o = {};
cat.forEach((item)=> {
if(item.id === id){
o = item;
return true;
}
if(item.goods) {
o = fin(id, item.goods);
return true;
}
});
return o;
}
console.log(fin(1, data));
console.log(fin(12, data));
3. 深拷贝与浅拷贝:
- 浅拷贝只拷贝一层, 更深层次对象级别的只拷贝引用;
- 深拷贝拷贝多层, 每一级别的数据都会拷贝;
Object.assign(target, ...source)
ES6新增方法(语法糖)可以实现浅拷贝;
var obj = {
id: 1,
name: '张三',
info: {
age: 18,
sex: "man"
},
colors: ["red", "pink", "green"]
}
var o = {};
function deep_copy(from, to) {
for (var k in from) {
// k: 属性名, obj[k]: 获取对应值
// 判断类型吧: 数组属于object
if(from[k] instanceof Array) {
to[k] = [];
deep_copy(from[k], to[k]);
} else if(from[k] instanceof Object) {
to[k] = {};
deep_copy(from[k], to[k]);
} else {
to[k] = from[k];
}
}
}
deep_copy(obj, o);
o.info = {hight: 180}
console.log(o);
console.log(obj);