JS手写系列1
参考文章:
【javascript】手写call,apply,bind函数
以下为一些常见的js手写代码系列
- call
- apply
- bind
- new
- 数组扁平化
- 数组去重
- 原型继承
一、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;