前言:
今天来梳理一下前端的几个高频面试题,我想正常前端程序员一般应该都知道答案吧。来看看今天的主要内容
this三种指向
this:谁调用我,我就指向谁
- 全局函数:this指向window
- 对象方法:this指向对象
- 构造函数:this指向new创建的空对象
this指向详解
/*
环境对象 this : 谁'调用'我,我就指向谁
普通函数; 函数名() this指向window
对象方法: 对象名.方法名() this指向对象
构造函数; new 函数名() this指向new创建实例对象
* 小技巧: 没点没new是window, 有new是实例,有点是点左边的对象
*/
//作用域链
let obj = {
name: "张三",
eat: function() {
//1级链
console.log(this) //1.obj
function fn() {
//2级链
console.log(this) //2.window
}
fn()
}
}
let eat = obj.eat
obj.eat()
call、apply、bind区别
上下文调用的三种方法:
- 函数名.call()
- 函数名.apply()
- 函数名.bind()
相同点:都是修改函数this指向
不同点:
- 传参方式不同:call用于单个参数,apply用于多个参数
- 执行机制不同:
- (1)call与apply会立即执行,bind不会立即执行
- (2)call、apply用一次修改一次;bind一次修改终身有效
call()调用函数
众所周知,默认情况下,函数内部的this无法被修改,如果想要动态的修改函数内部的this指向,则需要使用上下文调用的方法(上下文:函数作用域;上下文指向:修改函数作用域内部指向) call()的用法:语法:函数名.call(修改的this,参数1,参数2......)
function fn(a, b) {
console.log(this);
console.log(a + b);
}
fn()
new fn()
//函数名.call(修改的this,参数1,参数2......)
fn.call({
name: '隔壁老王'
}, 20, 30)
call()的应用场景:万能数据类型检测。
typeof数据检测数据类型有两种数据类型检测不了,分别是数组和null得到的都是'object'- 万能数据类型检测:
Object.prototype.toString.call(数据)
//值类型
let str = 'abc'
let num = 123
let bol = true
let und = undefined
let nul = null
//引用类型
let arr = [10, 20, 30]
let fn = function () {}
let obj = {
name: '张三'
}
console.log(typeof str); //string
console.log(typeof num); //number
console.log(typeof bol); //boolean
console.log(typeof und); //undefined
console.log(typeof nul); //object
console.log(typeof arr); //object
console.log(typeof fn); //function
console.log(typeof obj); //object
/*
万能的数据类型检测原理
(1)Object.prototype.toString()内部会返回的this的固定类型,得到固定格式字符串'[object, 数据类型]'
(2)使用Object原型中的toString()要想得到数据类型,只需要把this的指向修改成你想检测的对象
*/
console.log(Object.prototype.toString.call(str)); //[object String]
console.log(Object.prototype.toString.call(num)); //[object Number]
console.log(Object.prototype.toString.call(bol)); //[object Boolean]
console.log(Object.prototype.toString.call(und)); //[object Undefined]
console.log(Object.prototype.toString.call(nul)); //[object Null]
console.log(Object.prototype.toString.call(arr)); //[object Array]
console.log(Object.prototype.toString.call(fn)); //[object Function]
console.log(Object.prototype.toString.call(obj)); //[object Object]
apply()调用函数
语法:函数名.apply(修改的this, 数组/伪数组);apply会自动遍历数组和伪数组,然后逐一传参
function fn(a, b) {
console.log(this);
console.log(a + b);
}
fn()
new fn()
//(1) 函数名.call(修改的this,参数1,参数2......)
fn.call({
name: '隔壁老王'
}, 20, 30)
//(2)函数名.apply(修改的this,数组/伪数组)
// apply会自动遍历数组和伪数组,然后逐一传参
fn.apply({
name: '王五'
}, [50, 60])
apply的应用场景:伪数组转真数组(伪数组:有数组的三要数(下标、元素、长度),不能使用数组的方法,本质是对象)
let obj = {
0: 10,
1: 20,
2: 30,
length: 3
}
console.log(obj);
//需求:有时候需要使用真数组 需要把伪数组转换成真数组
let arr = []
//arr.push.apply(arr,伪数组)
//这里使用apply不是为了改变this的指向,而是利用apply自动遍历数组传参的特性把伪数组转换成真数组
arr.push.apply(arr, obj)
console.log(arr);
//不过有了ES6新语法之后就很少用了
//ES6: 伪数组转换真数组,固定静态方法 Array.from(伪数组)
let newArr = Array.from(obj)
console.log(newArr);
bind()调用函数
语法:函数名.bind(修改的this);bind()不会立即执行函数,而是得到一个修改之后的函数,用于定时器、事件处理函数
function fn(a, b) {
console.log(this);
console.log(a + b);
}
fn()
new fn()
//(1) 函数名.call(修改的this,参数1,参数2......)
fn.call({
name: '张三'
}, 20, 30)
//(2)函数名.apply(修改的this,数组/伪数组)
// apply会自动遍历数组和伪数组,然后逐一传参
fn.apply({
name: '李四'
}, [50, 60])
//函数名.bind(修改的this)
//bind不会立即执行函数,而是得到一个修改this指向之后的一个新函数
let newFn = fn.bind({
name: '王五'
})
newFn(22, 33)
bind()应用场景:修改定时器的this(定时器中的this默认指向window,如果要修改定时器的this,就需要使用bind)
setTimeout(function () {
console.log(this);
}.bind({name: '张三'}), 2000)
闭包
- 什么是闭包:
- 第一种接地气的说法:闭包是一个访问其他函数内部变量的函数
- 第二种听起来高大上一点:闭包是 函数 + 上下文引用
- 闭包作用:解决变量污染
function fn() {
//局部变量
let num = 10
//fn1 + num 的组合就是闭包
function fn1() {
console.log(num);
console.log('age');
}
fn1()
}
fn()
断点调试一下,更加清晰,红框所示就是闭包
递归
- 什么是递归:函数自己调用自己
- 递归场景:(1)深拷贝(2)遍历dom树
- 什么是深拷贝和浅拷贝:
- 浅拷贝:拷贝地址,修改拷贝后的数据对原数据有影响
- 深拷贝:拷贝数据,修改拷贝后的数据对原数据无影响
- 浅拷贝深拷贝的两种实现方式
- json方式:
let newObj = JSON.parse(JSON.stringify(js对象))(json底层自动深拷贝) - 递归函数:见下文 浅拷贝与json方式深拷贝代码
let obj = {
name: '张三',
age: 20,
sex: '男',
hobby: ['吃饭', '睡觉', '打工']
}
//浅拷贝:拷贝地址
/* let newObj = obj
newObj.name = '李四'
console.log(obj, newObj); */
//json深拷贝
//(1)JSON.stringify(js对象):把js对象 —> json字符串(json底层自动深拷贝)
let newObj = JSON.parse(JSON.stringify(obj))
newObj.name = '李四'
newObj.hobby[0] = '打豆豆'
console.log(obj, newObj);
递归方式进行深拷贝,在代码里进行说明。
let obj = {
name: '张三',
age: 20,
sex: '男',
hobby: ['吃饭', '睡觉', '学习'],
star: {
name: "迪丽热巴",
height: '175cm'
}
}
//深拷贝函数封装
function kaobei(obj, newObj) {
//key in遍历对像
for (let key in obj) {
//如果检测到是对象属性是数组,那么进行递归,
//直到把数组完全复制一份再进行下一个对象属性遍历
if (obj[key] instanceof Array) {
//声明新数组
newObj[key] = []
//这里key值是hobby,重新传参进行递归,将hobby复制到新的数组里
kaobei(obj[key], newObj[key])
} else if (obj[key] instanceof Object) {
//声明空对象
newObj[key] = {}
//对象的拷贝跟数组方式一样
kaobei(obj[key], newObj[key])
} else {
//将obj里的属性值给newObj里的属性
newObj[key] = obj[key]
}
}
}
//(1)创建一个空对象
let newObj = {}
//(2)调用封装函数
kaobei(obj, newObj)
//现在修改newobj不会导致obj也一起修改了
newObj.name = '李四'
newObj.hobby[0] = '打豆豆'
console.log(obj, newObj);
这里打断点会有更加直观的体会。