目录
一、改变函数内 this 指向
在讲方法前,我们先来了解 this 指向的几种情况。
调用方式 this指向 普通函数调用 window 构造函数调用 实例对象 原型对象里面的方法也指向实例对象 对象方法调用 该方法所属对象 事件绑定方法 绑定事件对象 定时器函数 window 立即执行函数 window
这些 this 的指向,是当我们调用函数的时候确定的。调用方式的不同决定了 this 的指向不同,一般指向我们的调用者.
1. call()
var o = {
name: 'andy'
}
function fn(a,b) {
console.log(this);
console.log(a + b);
};
fn.call(o,1,2); // this 指向 对象 o , 输出的结果为 {name:'andy'} 3
作用:
1.call 第一个可以调用函数 第二个可以改变函数内的 this 指向
2.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,'男');
//打印结果就是 刘德华 18 男
2. apply()
apply() 方法 调用 一个函数。简单理解为调用函数的方式,但是它可以改变函数的 this 指向。
fun.apply(thisArg, [argsArray])
thisArg: 在 fun 函数运行时指定的 this 值
argsArray: 传递的值,必须包含在 数组(伪数组) 里面
返回值就是函数的返回值,因为它就是调用函数
var o = {
name: 'andy'
};
function fn(arr) {
console.log(this);
console.log(arr);
};
fn.apply(o,['pink']); //this指向 o,输出为 {name:'andy'} pink
注意:apply 的主要应用 比如说我们可以利用 apply 借助于数学内置对象求最大值/最小值
代码如下👇
var arr = [1,66,3,99,4];
var max = Math.max.apply(Math,arr);
//apply 第一个参数为 null 或 指向数学内置对象,因为是Math调用的 max 函数,后者更好
console.log(max); //结果为 99
3. bind() ⭐
bind()方法不会调用函数,但是能改变函数内部 this 指向
fun.bind(thisArg, arg1,arg2,...)
thisArg: 在 fun 函数运行时指定的 this 值
arg1,arg2: 传递的其他参数
返回由指定的 this 值和初始化参数改造的 原函数拷贝
var o = {
name: 'andy'
};
function fn(a,b) {
console.log(this);
console.log(a+b);
};
var f = fn.bind(o,1,2);
f();
注意:用 f 来接收拷贝的函数,调用即可输出 this 指向的对象,不调用没输出
bind()方法配合定时器使用如下👇 (实现当按钮按下时,按钮变为不可点击状态,三秒后再恢复)
var btn1 = document.querySelector('button');
btn1.onclick = function(){
this.disabled = true; //这个 this 指向的是 btn 这个按钮
setTimeout(function(){
this.disabled = false; //本来定时器函数里面的 this 是应该指向 window的,但因为调用了bind,此时指向btn
}.bind(this),3000); //这个 this 指向的是 btn 这个对象
}
4. 三个方法总结
相同点:
都可以改变函数内部的 this 指向
区别点:
1. call 和 apply 会调用函数,并且改变函数内部 this 指向
2. call 和 apply 传递的参数不一样,call 传递参数 aru1,aru2..形式, apply必须数组形式[arg]
3. bind 不会调用函数,可以改变函数内部 this 指向
主要应用场景:
1. call 经常做继承
2. apply 经常跟数组有关系,比如借助于数学对象实现最大值最小值
3. bind 不调用函数,但是还想改变 this 指向,比如改变定时器内部的 this 指向
二、闭包
闭包(closure)指有权 访问 另一个函数作用域中 变量 的 函数
简单理解就是,一个作用域可以访问另外一个函数内部的局部变量
举个例子👇
function fn() {
var num = 5;
function fun() {
console.log(num);
}
fun();
}
fn(); //输出结果为 5
很显然 fn 和 fun是不同作用域下的,但fun 能访问 fn 里面的局部变量,所以 fn 就是闭包
闭包的主要作用: 延伸了变量的作用范围直到所有函数都调用完了所拿到的局部变量,局部变量才会被销毁
闭包常问的面试题:利用闭包的方式得到当前小li的索引号
var lis = document.querySelectorAll('li');
for(var i = 0; i < lis.length;i++) {
//利用 for 循环创建了 4 个立即执行函数
//立即执行函数也称为小闭包因为立即执行函数里面的任何一个函数都可以使用它的i这变量
(function(i){
lis[i].onclick = function() {
console.log(i);
}
})(i);
}
但是这个方法比创建自定义属性要复杂也麻烦,因为这个方法一共创建了多个立即执行函数,且 每个局部变量 i 都必须等 点击事件结束后才能释放,如果不点击就不释放,导致内存泄露。
所以 闭包不一定好,要合理的利用
三、递归
什么是递归?
如果 一个函数在内部可以调用其本身, 那么这个函数就是 递归函数
简单理解: 函数内部自己调用自己,这个函数就是递归函数
递归函数的作用和循环效果一样
由于递归很容易发生 ”栈溢出“ 错误(stack overflow),所以 必须要加退出条件 return
因为递归有点绕,博主一开始学的时候被绕晕了好几次,所以博主就多举几个例子,如果你都能看懂了,说明你已经掌握的很棒啦~
① 利用递归函数求 1~n 的阶乘 1 * 2 * 3 * 4 ...n
function fn(n) {
if(n == 1) {
return 1;
}
return n * fn(n-1);
//假设我们传进入的 n = 3,先判断 n是否等于1,不等于执行 return n* fn(n-1);
//所以第一次进入的结果为 return 3 * fn(3-1);
//其中 fn(3-1) 又进入函数 继续判断 不等于 1 所以返回值为 2 * fn(2-1);
//fn(2-1)继续调用函数,此时 n = 1, return 1
// 把上面所有的 fn(n-1) 用 return返回的值代替为: 3 * 2 * 1 = 6
}
console.log(fn(3));
② 利用递归函数求斐波那契数列(兔子序列) 1、1、2、3、5、8、13、21...
//我们只需要知道用户输入的 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));
②运行步骤也是和①一样的,你们可以试试自己在草稿上写一写.如果上面两个都会了,那就再看看递归是怎么遍历数据的把
③ 输入 id 号就返回对应的数据对象
var data = [{
id:1,
name: '家电',
goods:[{
id:11,
gname: '冰箱'
},{
id:12,
gname: '洗衣机'
}]
},{
id:2,
name: '服饰'
}];
function getID(json,id) {
var o = {};
json.forEach(function(item){
if(item.id == id) {
o = item;
// return 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));
四、ES6中的let和const
为什么使用 ES6:
每一次标准的诞生都意味着语言的完善,功能的加强。 JavaScript语言本身也有一些令人不满意的地方变量提升特性增加了程序运行时的不可预测性
语法过于松散,实现相同的功能,不同的人可能会写出不同的代码
1. let关键字
ES6中新增的用于声明变量的关键字, let 声明的变量只在所处于的块级有效
if(true) {
let a = 10;
}
console.log(a) // a is not defined
注意:❗
① 使用let关键字声明的变量才具有块级作用域,使用 var 声明的变量不具备块级作用域特性
好处: 防止循环变量变成全局变量
for(let i = 0; i < 5; i++) {
}
console.log(i); // i is not defined
② 不存在变量提升
console.log(a); // a is not defined
let a = 20;
③ 暂时性死区(外部定义同名的变量对块级作用域的let没有影响);
var tmp = 123;
if(true) {
tmp = 'abc';
let tmp;
console.log(tmp);// tmp is not defined
}
let 关键字 面试题👇
👆 var 定义的变量, 👇 let 定义的变量
2. const关键字
作用: 声明常量,常量就是值(内存地址)不能变化的量
特点:❗
① 具有块级作用域
if(true) {
const a = 10;
}
console.log(a); // a is not defined
② 声明常量时必须赋值
const PI; //Missing initializer in const declaration (没有赋值)
③ 常量赋值后,值不能修改,对于复杂数据类型,如数组,更改对应的索引号中的内容是可以 的,但是重新赋值就不行,即内存地址不可更改
const ary = [100,200];
ary[0] = 'a';
ary[1] = 'b';
console.log(ary); // ['a','b'];
ary = ['a','b']; // Assignment to constant variable
3. var 、let 、const 的区别
var | let | const |
---|---|---|
函数级作用域 | 块级作用域 | 块级作用域 |
变量提升 | 不存在变量提升 | 不存在变量提升 |
值可更改 | 值可更改 | 值不可更改 |
五、ES6解构赋值
ES6中允许从数组中提取值,按照对应位置,对变量赋值。对象也可以实现解构
1. 数组解构
let [a,b,c] = [1,2,3];
console.log(a); // 1
console.log(b); // 2
console.log(c); // 3
如果解构不成功,变量的值为 undefined 👇
let [bar,foo] = [1];
console.log(foo); // undefined
console.log(bar); // 1
2. 对象解构
let person = {name: 'zhangsan', age: 20};
let {name,age} = person;
console.log(name); // 'zhangsan'
console.log(age); // 20
//----------------写法二👇--------------
let {name: myName,age: myAge} = person; //myName myAge 属于别名
console.log(myName); // 'zhangsan'
console.log(myAge); // 20
六、ES6箭头函数
箭头函数是用来简化函数定义语法的
() => {}
const fn = () => {}
特点:❗
① 函数体中只有一句代码,且代码的执行结果就是返回值,可以省略大括号
function sum(num1,num2) {
return num1 + num2;
}
//----------下面代码等同于上面------
const sum = (num1,num2) => num1 + num2;
② 如果形参只有一个,可以省略小括号
function fn(v){
return v;
}
//--------下面代码等同于上面--------
const fn = v => v;
箭头函数中的 this
箭头函数不绑定 this 关键字,箭头函数中的 this,指向的是函数定义位置的上下文 this
const obj = {name: 'zhangsan'}
function fn() {
console.log(this);
return () => {
console.log(this)
}
}
const resFn = fn.call(obj);
resFn(); // this 指向为 obj , 输出为 {name: 'zhangsan'}
箭头函数面试题
var obj = {
age: 20,
say: () => {
alert(this.age)
}
}
obj.say(); //输出为 undefined
obj 对象不能产生作用域,所以 this 相当于被定义在全局作用域下,所以返回值为 undefined
七、剩余参数和拓展运算符
1. 剩余参数 (...args)
① 剩余参数 ...args 允许我们将一个不定数量的参数表示为一个数组
function sum(first, ...args){
console.log(first); //10
console.log(args); // [20,30]
}
sum(10,20,30)
② 把获取来的值全部放到一个数组里 再用 forEach遍历,这样就可以传入不同个数的实参
const sum = (...args) => {
let total = 0;
args.forEach(item => total += item);
return total;
};
console.log(sum(10,20)); // 30
console.log(sum(10,20,30)); //60
剩余参数和解构搭配使用
只要在变量前面加 ... 就表示这个为数组,它用来接收 剩余参数
let students = ['wangwu','zhangsan','lisi'];
let [s1, ...s2] = students;
console.log(s1); // 'wangwu'
console.log(s2); // ['zhangsan','lisi']
2. 扩展运算符(展开语法)
扩展运算符可以将数组或者对象转为用逗号分隔的参数序列
只要在数组名或对象名前面加 ... 即可
let ary = [1,2,3];
...ary // 1,2,3
console.log(...ary); // 1 2 3
//用 console.log 输出的时候是没有逗号分隔的,因为 参数序列的逗号被当作 console.log 方法的参数分隔符了,等同于 console.log(1,2,3);所以输出结果为 1 2 3
扩展运算符应用:合并数组
// 方法一
let ary1 = [1,2,3];
let ary2 = [3,4,5];
let ary3 = [...ary1, ...ary2];
// 方法二
ary1.push(...ary2);
扩展运算符应用:将伪数组转换为真正的数组
【伪数组类似于: 从页面中获取到的很多 div 标签,存放在伪数组中,通过 转换为数组,这样就 可以调用数组中的方法了 】
let oDivs = document.getElementsByTagName('div');
oDivs = [...oDivs];
八、Array实例方法
1. Array.from()
将类数组或可遍历对象转换为真正的数组
let arrayLike = {
'0':'a',
'1':'b',
'2':'c'
};
let arr2 = Array.from(arrayLike); // ['a','b','c']
方法还可以接受第二个参数,作用类似于数组的map方法,用来对每个元素进行处理,将处理后的值放入返回的数组
let arrayLike = {
'0': 1,
'1': 2,
'length': 2
}
let newAry = Array.from(aryLike,item => item * 2)
// item是当前遍历的对象,将每个对象的值 乘 2
2. find()
用于找出第一个符合条件的数组成员,如果没有找到就返回 undefined
let ary = [{
id: 1,
name: '张三'
}, {
id: 2,
name: '李四'
}];
let target = ary.find((item,index) => item.id == 2);
console.log(target); // {id: 2, name: "李四"}
3. findIndex()
用于找出第一个符合条件的数组成员的位置,如果没有找到返回 -1
let ary = [1,5,10,15];
let index = ary.findIndex((value, index) => value > 9);
console.log(index); //2
4. includes()
表示某个数组是否包含给定的值,返回布尔值
[1,2,3].includes(2) //true
[1,2,3].includes(4) //false
九、set数据结构
1. startsWith()、endsWith()
startsWith(): 表示参数字符串是否在原字符串的头部,返回布尔值
endsWith(): 表示参数字符串是否在原字符串尾部,返回布尔值
let str = 'Hello world!';
str.startsWith('Hello') // true
str.endsWith('!') // true
2. repeat()
repeat方法表示将原字符串重复 n 次,返回一个新字符串
'x'.repeat(3) //'xxx'
'hello'.repeat(2) // 'hellohello'
3. Set数据解构
ES6提供了新的数据结构 Set. 它类似于数组,但是成员的值都是唯一的,没有重复的值
Set本身是一个构造函数,用来生成 Set 数据结构
const s = new Set();
Set函数可以接受一个数组作为参数,用来初始化
const set = new Set([1,2,3,4]);
利用Set数据解构做数组去重
const s3 = new Set(['a','a','b','b']);
console.log(s3.size) // 2 因为重复的部分会被去掉
const ary = [...s3];
console.log(ary) //['a','b']
Set实例方法
add(value): 添加某个值,返回 Set 结构本身
delete(value): 删除某个值,返回一个布尔值,表示删除是否成功
has(value): 返回一个布尔值,表示该值是否为 Set 的成员
clear(): 清除所有成员,没有返回值
const s = new Set();
s.add(1).add(2).add(3); //向 set 结构中添加值
s.delete(2) //删除 set 结构中的2值
s.has(1) //表示 set 结构是否有 1 这个值 返回布尔值
s.clear() //清除 set 结构中的所有值
遍历Set
Set 结构的实例与数组一样,也拥有forEach方法,用于对每个成员执行某种操作,没有返回值
s.forEach(value => console.log(value))
本次的分享到这就结束啦~