前言
又到了跳槽找工作的旺季,近几天整理了一些面试常考题,希望对你有所帮助,会的可以温习一下,不会的可以好好学习一下。欢迎点赞支持,共同学习成长!由于本文代码太多,掘金有总字符限制,分为2篇来写。
call 方法实现
实现详情
所有的方法都可以调用
call/apply/bind方法, 它们是函数原型上的方法,因此需要挂载到Function.prototype
下面我们来动手实现一下我们自己的 call/apply/bind方法
- 需要注意一下事项
- 如果第一个参数没有,那么在非严格模式下默认指向
window或者global- 传入的第一个参数是
this指向的对象,根据this绑定规则,我们知道context.fun,fun中的this被隐式绑定到context上,因此可以使用 context.fun(...args) 来执行函数- 原本
context并不存在fun属性,函数执行结束后删除fun- 返回函数执行结果
Function.prototype.myCall = function( ){
const [ context, ...args ] = [ ...arguments ]
// 判断传入的第一个参数是否存在,如不存在,在非严格模式下根据运行环境 取window 或者 global
if( !context){
context = typeof window !== 'undefined' ? window : global;
}
// this的指向是当前函数fun
context.fun=this
let result = context.fun(...args)
//原本context并不存在fun属性,函数执行结束后删除fun
delete context.fun
return result
}
apply 的实现
实现详情
call 与 apply 的区别
call 可以有2个以上参数,
apply 最多有两个参数,且第二个参数只能是数组或类数组
- fun.call(context,arg1,arg2,arg3,...) : 第一个参数是 this 指向的对象,其他参数依次传入
- fun.apply(context,[args]) : 第一个参数是 this 指向的对象,第二个参数是数组或者类数组
Function.prototype.myApply = function (context,args){
// 判断传入的第一个参数是否存在,如不存在,在非严格模式下根据运行环境 取window 或者 global
if(!context){
context = typeof window !== 'undefined' ? window : global;
}
// this的指向是当前函数fun
context.fun = this;
let result
//根据第二个参数返回不同的执行结果
if(!args){
result = context.fun()
}else{
result = context.fun(...args)
}
//原本context并不存在fun属性,函数执行结束后删除fun
delete context.fun
return result
}
bind 实现
实现详情
与 call/apply 不同 bind 返回的是一个函数
Function.prototype.myBind = function(){
let [context,args] = [...arguments]
let _this = this;
return function Fun(){
//考虑使用new的情况
if(this instanceof Fun){
return new _this(...args,...arguments)
}
return _this.apply(context,args.concat(arguments))
}
}
深拷贝/浅拷贝区别,实现深拷贝
实现详情
深拷贝和浅拷贝是针对复杂数据类型来说的,浅拷贝只拷贝一层,而深拷贝是层层拷贝
深拷贝
深拷贝对于基本变量是直接复制其值,对于非基本变量则层层递归至基本变量后再复制。深拷贝后的结果与原始对象完全隔离互不影响,修改任意一个对象都不会影响另一个对象。
浅拷贝
浅拷贝是将对象的第一层属性进行复制,如果属性值是引用类型时,复制其地址;当引用地址指向的值发改变时,浅拷贝的结果也跟着变化。
可以通过扩展运算符 ... 或者 Object.assign 等实现浅拷贝
let foo = {
a: 1,
b: 2,
c: 3,
d: ['e','f','g']
};
let foo1 = Object.assign({},foo)
let foo2 = {...foo}
foo.a = 5
foo.d.push('h')
console.log(foo) // { a: 5, b: 2, c: 3, d: [ 'e', 'f', 'g', 'h' ] }
console.log(foo1) // { a: 1, b: 2, c: 3, d: [ 'e', 'f', 'g', 'h' ] }
console.log(foo2) // { a: 1, b: 2, c: 3, d: [ 'e', 'f', 'g', 'h' ] }
可以看出当第一层的属性值是基本类型时,修改原始对象不会影响新的对象,如果第一层的属性值是复杂数据类型时,新对象和原始对象的该属性值指向同一个内存地址,其中一个发生变化就会影响另一个;
深拷贝实现
对于复杂类型的数据实现深拷贝,需要层层深拷贝;而浅拷贝只拷贝一层
- 基本类型变量直接复制
- 非基本类型变量则递归至基本类型变量后再复制
- 深拷贝后对象与原对象完全隔离,互不影响
最简单深拷贝实现: JSON.parse(JSON.stringify(target))
JSON.parse(JSON.stringify(target)) 虽然简单,但部分情况下无法拷贝:
- 会忽略
undefined - 会忽略
Symbol - 不能处理正则 RegExp
- 不能正确处理 Date类型数据
- 对象属性是function时无法拷贝
- 原型链上的属性无法拷贝
自己动手实现一个简单的 deepClone 函数,需要注意以下几点:
- 如果是基本类型数据直接返回
- 如果是复杂数据类型则递归
- 如果是
RegExp或者Date类型,返回对应的类型 - 考虑循环引用问题
- 考虑Symbol
function deepClone(target,wmap = new WeakMap()){
if(target ===null || typeof target !== 'object'){
//如果是基本类型直接返回
return target;
}
if(target instanceof RegExp) return new RegExp(target)
if(target instanceof Date) return new Date(target)
if(wmap.has(target)){ //考虑引用
return wmap.get(target)
}
let res = new target.constructor(); //如果target是Array 则 target.constructor为 [Function: Array]
//如果target是Object 则 target.constructor为 [Function: Object]
wmap.set(target,res);
let keys=[...Object.getOwnPropertyNames(target),...Object.getOwnPropertySymbols(target)]
for(let key in keys){
//递归
res[key] = deepClone(target[key],wmap)
}
return res
}
new 的实现原理
实现详情
- 创建一个空的对象;
- 将新建对象作为构造函数的实例,构造函数中的this指向这个空对象
- 执行构造函数方法,将构造函数中的属性和方法添加到引用的对象上
- 如果构造函数执行的结果不是一个对象,则返回新建的对象,否则返回执行结果
function myNew(){
//创建一个新对象
let target = {}
//解构出 构造函数及参数
const [ constructor, ...args ] = [ ...arguments ]
//将创建的对象作为构造函数的实例
target.__proto__ = constructor.prototype
//执行构造函数,将构造函数的属性和方法添加到新建的对象上
let result = constructor.myApply(target, args)
//如果构造函数返回的不是一个对象 则返回新建的对象
return result instanceof Object ? result : target
}
- 验证
New实现结果
function test(name){
this.name=name
}
let date = myNew(test,2134474774)
let date2 = new test(2134474774)
console.log(date,date2)
JSONP 实现
实现详情
JSONP为前端实现跨域的一种方式,其原理是利用
html的script标签的src属性不受浏览器同源策略束缚,可以任意获取服务器脚本并执行。
JSONP正是利用这一特性,动态创建script标签来实现跨域的。
- 缺点
是只支持get请求,并且也需要后端做相应的处理
- 实现步骤
- 创建script标签
- 创建挂载到window上的callback方法
- 拼接URL并赋值给script的src属性
- 服务端处理请求,并把数据放入前端传来的callback回调函数中返回给前端
- 前端解析并执行服务端返回的方法调用
function jsonp({url,params,callback}) {
return new Promise((resolve,reject)=>{
//创建script标签
let script = document.createElement('script')
//把回调函数挂载到window上
window[callback] = function (data) {
resolve(data)
//执行结束,删除之前创建的script标签
document.body.removeChild(script)
}
//拼接url
params = {...params,callback}
let fields=[]
for(let key in params){
fields.push(`${key}=${params[key]}`)
}
script.src = `${url}?${fields.join('&')}`
document.body.appendChild(script)
})
}
- 测试验证需要写后端服务
function crosJsonp(data) {
console.log(data)
}
jsonp({
url:'http://localhost:8080/crosJsonpDemo',
params:{
dev:'test',
fileName:'json'
},
callback:'crosJsonp'
}).then(res=>{
console.log(res)
})
instanceOf 方法实现
实现详情
function instanceOf(suberType,superType) {
var left = suberType.__proto__;
var right = superType.prototype;
while(true){
if(left === null){
return false
}
if(left === right){
return true
}
left = left.__proto__
}
}
function Aoo(){}
function Foo(){}
Foo.prototype = new Aoo();//JavaScript 原型继承
var foo = new Foo();
console.log(instanceOf(foo,Foo),instanceOf(foo,Aoo)) // true true