常见JS手写代码系列总结1

JS手写系列1

参考文章:

JS常见手写代码题(一)

【javascript】手写call,apply,bind函数

以下为一些常见的js手写代码系列

  1. call
  2. apply
  3. bind
  4. new
  5. 数组扁平化
  6. 数组去重
  7. 原型继承

一、JS实现一个call

方法或函数fun.call(obj, 参数1,参数2,…),第一个值是改变this指向到obj,后面是参数队列,调用call立即执行方法fun

call的定义和用法

// call方法第一个参数指的是this的指向;接受一个参数列表;方法立即执行
// Function.prototype.call()样例
function fun(arg1, arg2) {
  console.log(this.name)
  console.log(arg1 + arg2)
}
const _this = { name: 'YIYING' }
// 这里把fun里的this,指向对象_this,然后立即执行,由此才可以输出YIYING
fun.call(_this, 1, 2)
// 输出
YIYING
3

手写实现call

Funcion.protoType.mockCall = function (context = window, ...args) {
	const key = Symbol()
	context[key] = this
	const result = context[key](...args)
	delete context[key]
	return result
}
或者:
Function.prototype.myCall = function(context) {
    if (typeof context === "undefined" || context === null) {
        context = window
    }
   //context=context||window  和上面的代码一样
    context.fn = this//(因为call的调用方式形如:myFun.call(obj),因此此时call方法的this指向为myFun,因此context.fn = this即为context.fn = myFun)
    const args = [...arguments].slice(1)//第一个参数为context,要去除
    const result = context.fn(...args)
    delete context.fn
    return result
}

实现分析

  • 首先context为可选参数,如果不传的话默认上下文是window
  • 接下来给content创建一个独一无二的属性(Symbol表示),并将值设置为需要调用的函数
  • 因为call可以传入多个参数作为调用函数的参数,这里用的…扩展运算符
  • 然后调用函数并将对象上的函数删除

二、JS实现一个apply

方法或函数fun.apply(obj, [参数1,参数2,…]),改变this指向到obj,立即执行方法fun

apply接受两个参数,第一个参数是要绑定给this的值,第二个参数是一个**参数数组。**apply和call实现类似,不同的就是参数的处理

Function.protoType.mockApply = function (context = window, args) {
	const key = Symbol()
	context[key] = this
	const result = context[key](...args)
	delete context[key]
	return result
}

三、JS实现一个bind

Function.prototype.bind 第一个参数是this的指向,从第二个参数开始是接收的参数列表。和call的区别在于bind方法返回值是函数以及bind接收的参数列表的使用。

实现思路:

  • 利用闭包保存调用bind时的this,这时的this就是原函数
  • 使用call/apply指定this
  • 返回一个绑定函数
  • 当返回的绑定函数被new运算符调用的时候,绑定的上下文指向new运算符创建的对象
  • 将绑定函数的prototype修改为原函数的prototype
Function.protoType.mockBind = function (context = window, ...initArgs) {
	const foo = this
	var bindFoo = function (...args) {
		if(this instanceof bindFoo){
      return new fn(...initArgs, ...args)
    }
		return foo.call(context, ...initArgs, ...args)
	}
	return bindFoo
}
简写:
Function.prototype.mockBind = function(ctx){
    let fn = this
    return function(){
        fn.apply(ctx, arguments) //arguments是函数调用时所传参数
    }  
}

第一个参数是this的指向,从第二个参数开始是接收的参数列表。区别在于bind方法返回值是函数以及bind接收的参数列表的使用。

四、手写一个new的实现

正常使用new

function Dog(name){
    this.name = name
}
Dog.prototype.sayName = function(){
    console.log('名字', this.name)
}
var dog1 = new Dog('小狗')
dog1.sayName() // 输出名字 小狗

思考一下new 操作符做了哪些事情?

  • 创建一个新对象
  • 新对象会被执行 __proto__链接,关联到构造函数的.prototype 属性上,即和构造函数用的一个原型,从而可调用原型上的方法
  • 函数调用的this绑定到新对象上
  • 如果函数没有返回其他对象,那么new表达式中的函数会调用自动返回这个新对象

手写new实现

function mockNew (foo, ...args) {
	if (typeof foo !== 'function') {
    throw Error('foo is not a constructor')
  }
	const obj = Object.create(foo.protoType)
	const result = foo.apply(obj, args)
	return typeOf result === 'object' && result !== null ?  result : obj
}
new的具体步骤
1. 创建一个空对象 var obj = {}
2. 修改obj.__proto__=Dog.prototype
3. 只改this指向并且把参数传递过去,call和apply都可以
4. 根据规范,返回 null 和 undefined 不处理,依然返回obj

五、数组扁平化

方法一:es6 flat方法

var arr = [1,2,[3,4,[5,6,[7]]]]
arr.flat(Infinity) // [1,2,3,4,5,6,7]

方法二:递归

var flatArr = function(arr1) {
	let newArr = [];
  function getChild(arr) {
		for(let i = 0; i<=arr.length;i++) {
			if(arr[i] instanceof Array === false && arr[i]) {
				newArr.push(arr[i])
			} else if(arr[i]){
				getChild(arr[i])
			}
		}
	}
	getChild(arr1);
	return newArr;
}

// 调用:
var a = [[1,2,2], [6,7,8, [11,12, [12,13,[14]]], 10]];
console.log('水电费', flatArr(a))
// [1, 2, 2, 6, 7, 8, 11, 12, 12, 13, 14, 10]

方法三:正则

const res2 = JSON.stringify(arr).replace(/\[|\]/g, '').split(',');
res2.map(item=> parseInt(item))

六、数组去重

方法一:es6 Set

var arr = [1,2,3,3,4,4,5]
var newArr = Array.from(new Set(arr)); // [1,2,3,4,5]
// 或者arr = [...set]      Array.from() 将伪数组转换为数组

方法二:循环遍历数组

function filterArr(arr){
	var newArr = [];
	arr.forEach(item => {
		if(!newArr.includes(item)) { // 也可以是!newArr.indexOf(item)
			newArr.push(item)
		}
	})
	return newArr
}

方法三:hash表

let arr = [1,1,2,3,2,1,2]
function unique(arr){
    let obj = {}
    arr.forEach((item) => {
        obj[item] = true
    })
    let keys = Object.keys(obj)
    keys = keys.map(item => parseInt(item)) // 转为数字
    return keys
}
console.log(unique(arr))

七、原型继承(寄生组合继承)

这里只写寄生组合继承了,中间还有几个演变过来的继承但都有一些缺陷
function Parent() {
  this.name = 'parent';
}
function Child() {
  Parent.call(this);
  this.type = 'children';
}
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值