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); // function
console.log(a instanceof Function); // true
console.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 运行结果为3
fn.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 运行结果为3
fn.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 = true
setTimeout((() => {
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)); // 23
console.log(car.yd(true)); // 33
作用域中的局部变量一直被使用着
function getNum(){
var n = 0
function add() {
return n++
}
return add
}
var c = getNum()
console.log(c()); // 0
console.log(c()); // 1
console.log(c()); // 2
6 递归
6.1 阶乘
//利用递归函数求1~n的阶乘 1 * 2 * 3 * 4 * ..n
function 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)