js常见手写功能
一,call 函数 apply 实现及常用范围
1,call
let Person = {
name:'孙三',
say(){
console.log(`my name is ${this.name}`);
}
}
let son = {
name:'孙琬'
}
Function.prototype.myCall = function(context){
console.log(this); // 谁调用谁就是this,此时调用者为Person.say,故this当为此函数
console.log(context); // context是调用传入的对象
// 因为此方法是要改变调用者的指向,因此这个this必定为一函数
if (typeof this !== 'function')
{throw new Error('error')
}
// 调用时如果传入参数,context 就是要改变的对象 不然此时的context 当为window
context = context || window
// 拿到除了第一个参数之外的参数
let args = [...arguments].slice(1)
console.log(args);
context.fn = this //关键点 【假设在此对象context上有一函数fn让他是当前调用的函数,】
context.fn(...args) // 调用fn 即为this执行函数
delete context[fn] // 应及时将增加的fn方法删除
}
Person.say.myApply(son,[2,2,3])
call函数可用于构造函数继承 类型判断
Object.prototype.toString.call({}) // '[object Object]'
Object.prototype.toString.call([]) // '[object Array]'
2,apply ( 接受数组 )
call 接收多个参数,第一个为函数上下文也就是this,后边参数为函数的参数,只是此参数需要传递数组。
// apply 写法一致只是传参为数组
Function.prototype.myApply = function(context){
if (typeof this !== 'function')
{throw new Error('error')
}
context = context || window
let args = [...arguments].slice(1)
context.fn = this //关键点 【假设在此对象context上有一函数fn让他是当前调用的函数,】
context.fn(...args) // 调用fn 即为this执行函数
delete context[fn] // 应及时将增加的fn方法删除
}
Person.say.myApply(son,[2,2,3])
apply 函数常用于函数的节流,防抖,去改变this 指向
debounce(fn,delay){
let timer
return function(...args){
if(timer){
clearTimeout(timer)
}
timer=setTimeout(()=>{
fn.apply(this,args)
},delay)
}
},
二,js继承方式
1, 原型链继承
重点:让父类的实例作为子类的原型
缺点:引用值类型的数据会被实例共享,只要某个实例加入某个属性,其他的实列上面也会有该属性
function Person(name,age){
this.name = name,
this.age = age
}
Person.prototype.run = function(){
console.log('跑步');
}
function Student(){
this.score = 99
}
Student.prototype = new Person()
console.log(new Student());
2,构造函数继承
重点:在子类构造函数中使用 call() 调用父类构造函数
优点:解决了原型链继承中不能传参且引用值共享问题
缺点:不能调用父类原型上的方法
function Person(name,age){
this.name = name,
this.age = age
}
Person.prototype.run = function(){
console.log('跑步');
}
function Student(name,age){
this.score = 99
Person.call(this,name,age)
}
console.log(new Student('张三',20));
3,组合式继承(构造函数和原型链)
重点:使用 call() 调用父类的属性,使用 new 获取父类原型上的方法
优点:解决了不能调用父类原型上的方法问题
function Person(name,age){
this.name = name,
this.age = age
}
Person.prototype.run = function(){
console.log('跑步');
}
function Student(name,age){
this.score = 99
Person.call(this,name,age)
}
Student.prototype = new Person()
console.log(new Student('张三',20));
三,深浅拷贝实现
1,引言:
基本数据类型的特点:直接存储在栈(stack)中的数据
引用数据类型的特点:存储的是该对象在栈中引用,真实的数据存放在堆内存里
引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。
浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。
2,浅拷贝实现方式
数据类型为对象:{...对象}
数据类型为数组:{...对象}
obj2 = Object.assgin(obj2, obj1)
3,深拷贝
obj2 = JSON.parse( JSON.stringify(obj1))
通过递归实现(推荐)
deepClone(obj){
if(!obj || typeof obj!=='object') return
let newObj = Array.isArray(obj) ? [] : {}
for (const key in obj) {
// 判断对象本身是否有此属性,而不是继承自原型
if (obj.hasOwnProperty(key)) {
newObj[key] = typeof obj[key] == 'object' ? this.deepClone(obj[key]) : obj[key]
}
}
return newObj
},
let obj = {
name: 'xiyang',
age: 25,
hoppy: {
type: 'wan',
num:{
n:2
}
}
}
let newobj = this.deepClone(obj)
newobj.name = 'lisi'
console.log(newobj); // obj:{ name:lisi, age:25 }
console.log(obj); // obj:{ name:xiyang, age:25 }
结语
一生思破红尘路,剑藏庐轩隐迷踪。