目录
实现一个_instance方法,判断对象obj是否是target的实例
实现一个myNew方法,接收一个构造函数以及构造函数的参数,返回构造函数创建的实例对象
寄生组合式继承:调用一次父类构造函数+通过object()复制原型
原型链相关
原型链(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");