0
点赞
收藏
分享

微信扫一扫

javaScript手写专题——实现instanceof/call/apply/bind/new的过程/继承方式

m逆光生长 04-10 09:30 阅读 2

目录

原型链相关

手写instanceof

实现一个_instance方法,判断对象obj是否是target的实例

 测试

 手写new的过程

实现一个myNew方法,接收一个构造函数以及构造函数的参数,返回构造函数创建的实例对象

测试myNew方法

手写类的继承

ES6:class+extends实现继承

组合继承:调用两次父类构造函数

Object.create原型式继承

寄生组合式继承:调用一次父类构造函数+通过object()复制原型

手写call/apply/bind方法

手写call方法

普通版call方法

 使用Symbol标识属性

处理非object类型绑定

测试call方法

手写apply方法

手写bind方法

 bind使用场景

普通版bind方法

用作构造函数boundFn处理

考虑构造函数继承

 测试bind方法


原型链相关

原型链(prototype chain)是 JavaScript 中面向对象编程的一个重要概念,用于实现对象的继承和共享属性。每个函数(构造函数)都有一个 prototype 属性,指向一个对象,这个对象称为原型对象。这个原型对象包含了所有实例共享的属性和方法。

当我们使用构造函数创建一个新对象时,新对象一个隐式的原型属性(__proto__,指向构造函数的原型对象。这样,新对象就可以通过原型链访问到构造函数原型对象上的属性和方法。

如果我们继续查找 __proto__ 属性,可以找到一个叫做 Object.prototype 的对象,它是所有对象的原型。如果继续查找 __proto__ 属性,会找到 null,表示原型链的结束。

这就形成了一个原型链的连接,从新对象的 __proto__ 属性可以一直向上查找到 Object.prototype,然后再查找到 null。这种连接方式让所有对象都可以继承 Object.prototype 的属性和方法,并且可以通过原型链实现对象的继承和共享属性。

手写instanceof

实现一个_instance方法,判断对象obj是否是target的实例

function _instanceof(obj, target) {
  //instanceof只检测对象
  if (typeof obj != "object" || obj == null) {
    return false;
  }
  let proto = Object.getPrototypeOf(obj); //拿到对象的原型
  //   let proto = obj.__proto__;
  while (proto) {
    if (proto == target.prototype) {
      //原型上找到了target
      return true;
    }
    proto = Object.getPrototypeOf(proto);
    // proto = proto.__proto__;
  }
  return false;
}

 测试

console.log(_instanceof(null, Array));
console.log(_instanceof([], Array)); //判断数组
console.log(_instanceof({}, Array));
console.log(_instanceof({}, Object)); //普通对象
const set = new Set();
console.log(_instanceof(set, Set)); //判断Set
const map = new Map();
console.log(_instanceof(map, Map)); //判断Map
const date = new Date();
console.log(_instanceof(date, Date)); //判断Date

 手写new的过程

实现一个myNew方法,接收一个构造函数以及构造函数的参数,返回构造函数创建的实例对象

//第一个参数构造函数,第二个通过...拿到的args参数
function myNew(constructor, ...args) {
  const obj = Object.create(constructor.prototype);
  let res = constructor.apply(obj, args); //使用apply绑定this,传args类数组对象,执行constructor构造函数方法
  //   let res = constructor.call(obj, ...args);
  return typeof res === "object" ? res : obj; //构造函数如果没有返回值,返回obj;如果有返回值,返回res
}

测试myNew方法

// 测试
function Person(name, age) {
  this.name = name;
  this.age = age;
}
Person.prototype.say = function () {
  console.log(this.age);
};
let person = myNew(Person, "三月的一天", 18);
console.log(person);
person.say();

手写类的继承

ES6:class+extends实现继承

class Parent {
  constructor(name) {
    this.name = name;
  }
  sayName() {
    console.log("parent", this.name);
  }
}
class Child extends Parent {
  constructor(name, age) {
    super(name); //继承父类构造函数
    this.age = age;
  }
  sayAge() {
    console.log("Child", this.age);
  }
}
const child = new Child("三月的一天", 18);
child.sayName(); //继承父类的fangfa
child.sayAge(); //自己的方法

组合继承:调用两次父类构造函数

function Parent(name) {
  this.name = name;
}
Parent.prototype.sayName = function () {
  console.log("parent", this.name);
};
function Child(name, age) {
  Parent.call(this, name); //通过call方法,手动让父类构造函数执行一遍
  this.age = age; //子类自己的属性
}
//先改变Child的原型
Child.prototype = new Parent(); //必须先将Child原型改成Parent,否则Child的方法会被Parent覆盖
//定义Child自己的原型方法
Child.prototype.sayAge = function () {
  console.log("Child", this.age);
};
const child = new Child("三月的一天", 18);
child.sayName();
child.sayAge();

Object.create原型式继承

let person = {
  name: "三月的一天",
  age: 18,
};
let anotherPerson = Object.create(person);

console.log(anotherPerson.name);//输出'三月的一天'
console.log(anotherPerson.age);
let person = {
  name: "三月的一天",
  age: 18,
};
let anotherPerson = Object.create(person, {
  name: {
    value: "新的名字",
  },
  sex: {
    value: "female",
  },
});

console.log(anotherPerson.name);//输出'新的名字'
console.log(anotherPerson.age);//18
console.log(anotherPerson.sex);//female

寄生组合式继承:调用一次父类构造函数+通过object()复制原型

function Parent(name) {
  this.name = name;
}
Parent.prototype.sayName = function () {
  console.log(this.name);
};
function Child(name, age) {
  Parent.call(this, name);
  this.age = age;
}

// Child.prototype = new Parent(); //组合继承
Child.prototype = Object.create(Parent.prototype);//寄生组合式继承
Child.prototype.constructor = Child;//手动修复Child.prototype的constructor

Child.prototype.sayAge = function () {
  console.log("Child", this.age);
};
const child = new Child("三月的一天", 18);
child.sayName(); //继承父类的fangfa
child.sayAge(); //自己的方法
console.log(child.__proto__ == Child.prototype);
console.log(Child.prototype.__proto__ == Parent.prototype);

手写call/apply/bind方法

具体使用哪个方法,取决于你的具体需求: 

  • 使用call:当你知道函数的参数是哪些,并且想要按顺序传递它们时。
  • 使用apply:当你知道函数的参数是一个数组,并且想要以数组的形式传递这些参数时。
  • 使用bind:当你需要一个新函数,新函数的this指向和参数已经确定时,或者当你需要动态地传递参数时。

手写思路:

手写call方法

 举一个使用call方法的例子:

var foo = {
    value: 1
};

function bar() {
    console.log(this.value);
}

bar.call(foo); // 1

普通版call方法

Function.prototype.myCall = function (context = window, ...args) {
  context.fn = this;
  let result = context.fn(...args);
  delete context.fn;
  return result;
};

 使用Symbol标识属性

Function.prototype.myCall = function (context = window, ...args) {
  let fn = Symbol(); //将fn属性名称定义为Symbol
  context[fn] = this; //通过[]访问Symbol类型变量,将this当前函数赋给value
  let result = context[fn](...args); //调用方法
  delete context[fn]; //删除属性fn
  return result;
};

处理非object类型绑定

//添加函数原型myCall方法
Function.prototype.myCall = function (context = window, ...args) {
  if (typeof context != "object") {
    //非object类型的,手动转object
    context = new Object(context);
  }
  let fn = Symbol(); //将fn属性名称定义为Symbol
  context[fn] = this; //通过[]访问Symbol类型变量,将this当前函数赋给value
  let result = context[fn](...args); //调用方法
  delete context[fn]; //删除属性fn
  return result;
};

测试call方法

手写apply方法

Function.prototype.myApply = function (context = Window, args) {
  //context myApply传入的对象
  //this 调用myApply的函数
  //args this需要的参数
  if (typeof context != "object") {
    context = new Object(context);
  }
  let fn = Symbol();
  context[fn] = this;
  let result = context[fn](...args); //实际函数的入参一定是全展开的
  delete context[fn];
  return result;
};

测试apply方法

 

手写bind方法

 bind使用场景

函数柯里化,将多参函数转换为单参函数

function add(a, b, c) {
  return a + b + c;
}
const add5 = add.bind(null, 5);
console.log(add5(3, 4)); // 12

 在类的构造函数中使用 bind() 函数来创建一个新的构造函数。

class MyClass {
  constructor(name) {
    this.name = name;
  }
  sayName() {
    console.log(this.name);
  }
}
const obj = {
  name: 'obj'
};
const boundClass = MyClass.bind(obj);
const boundObj = new boundClass('boundObj');
boundObj.sayName(); // 'boundObj'

 将事件监听器函数绑定到某个对象上,并在事件触发时保持对象的上下文。

const obj = {
  name: 'obj',
  handleEvent: function(event) {
    console.log(this.name + ' handled event: ' + event);
  }
};
const button = document.getElementById('myButton');
button.addEventListener('click', obj.handleEvent.bind(obj));

普通版bind方法

//myBind方法的调用形式let boundFn =fn.myBind(obj,...args)
//fn是对象想要使用的函数
//fn是调用方法的对象,args是bind时候传入的函数入参的部分或全部参数
Function.prototype.myBind = function (context = window, ...args) {
  const fn = this; //这里的this是fn
  function boundFn(...innerArgs) {
    //创建一个新函数boundFn
    return fn.apply(context, args.concat(innerArgs)); //执行fn,fn从闭包中的this获取,所以this要提前存给fn;fn的参数要将两次入参拼起来
  }
  return boundFn; //返回新函数 新函数执行boundFn(...innerArgs)
};

用作构造函数boundFn处理

//myBind方法的调用形式let boundFn =fn.myBind(obj,...args)
//fn是对象想要使用的函数
//fn是调用方法的对象,args是bind时候传入的函数入参的部分或全部参数
Function.prototype.myBind = function (context = window, ...args) {
  const fn = this; //这里的this是fn
  function boundFn(...innerArgs) {
    //创建一个新函数boundFn
    context = this instanceof boundFn ? this : context;//如果boundFn被当做构造函数,执行fn的对象就是当前的this
    return fn.apply(context, args.concat(innerArgs)); //执行fn,fn从闭包中的this获取,所以this要提前存给fn;fn的参数要将两次入参拼起来
  }
  return boundFn; //返回新函数 新函数执行boundFn(...innerArgs)
};

考虑构造函数继承

//myBind方法的调用形式let boundFn =fn.myBind(obj,...args)
//fn是对象想要使用的函数
//fn是调用方法的对象,args是bind时候传入的函数入参的部分或全部参数
Function.prototype.myBind = function (context = window, ...args) {
  const fn = this; //这里的this是fn
  function boundFn(...innerArgs) {
    //创建一个新函数boundFn
    context = this instanceof boundFn ? this : context; //如果boundFn被当做构造函数,执行fn的对象就是当前的this
    return fn.apply(context, args.concat(innerArgs)); //执行fn,fn从闭包中的this获取,所以this要提前存给fn;fn的参数要将两次入参拼起来
  }
  boundFn.prototype = Object.create(fn.prototype); //考虑fn也是构造函数,boundFn要继承
  return boundFn; //返回新函数 新函数执行boundFn(...innerArgs)
};

 测试bind方法

// 测试用例
function Person(age, job, gender) {
  console.log(this.name, age, job, gender);
}
var obj = {
  name: "三月的一天",
};
// let result = Person.myBind(obj, 22, "前端开发")("female");
let bindFn = Person.myBind(obj, 22);
bindFn("前端开发", "female");

 

举报

相关推荐

0 条评论