call,apply,bind方法实现
call方法实现
使用一个指定的 this 值和一个或多个参数来调用一个函数。
实现要点:
- this 可能传入 null;
- 传入不固定个数的参数;
- 函数可能有返回值;
Function.prototype.mycall=function(obj){
obj.fn=this;
obj.fn();
delete obj.fn;
}
console.log('mycall-------------')
fn.mycall(obj)
Function.prototype.mycall2=function(obj){
//判断是否是函数,只有函数有call方法
if(typeof this!=='function'){
throw new TypeError('not function')
}
//当call第一个参数为undefined或者null时,this默认指向window
obj=obj||window;
obj.fn=this;
let args=[...arguments].slice(1)
let res=obj.fn(...args)
delete obj.fn;
return res
}
console.log('mycall2-------------')
fn.mycall2(obj,222,111)
apply方法实现
apply 和 call 一样,唯一的区别就是 call 是传入不固定个数的参数,而 apply 是传入一个数组。
实现要点:
- this 可能传入 null;
- 传入一个数组;
- 函数可能有返回值;
Function.prototype.myapply=function(context){
if(typeof this !=='function'){
throw new TypeError('err')
}
context=context||window
context.fn=this
let args=[...arguments].slice(1)
let res;
if(args.length>0){
res=context.fn(...args[0])
}else{
res=context.fn()
}
delete context.fn
return res
}
bind方法实现
bind 方法会创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。
实现要点:
- bind() 除了 this 外,还可传入多个参数;
- bing 创建的新函数可能传入多个参数;
- 新函数可能被当做构造函数调用;
- 函数可能有返回值;
//不同于call和apply方法,bind方法返回的是一个函数
Function.prototype.mybind=function(context){
if(typeof this!=='function'){
throw new TypeError('err')
}
let _this=this
let args=[...arguments].slice(1)
// instanceof用来检测某个实例对象的原型链上是否存在这个构造函数的prototype属性,
// this instanceof func === true时,说明返回的fBound被当做new的构造函数调用,此时this=fBound(){},
//否则this=window, 如果是的话使用新创建的this代替硬绑定的this
return function F(){
if(this instanceof F){
return new _this(...args,...arguments)
}else{
return _this.apply(context,args.concat(...arguments))
}
}
}
实现new方法
new操作符的原理: 1.首先创建一个新对象 2.将这个对象链接到其原型 3.将this绑定到新创建的对象 4.返回新对象
function createNew(){
let obj={}//新建对象
//所以需要用一个数组对象[],调用shift方法,然后将arguments作为第一个参数传入
//即将对象arguments中的this指向Array类型,然后调用Array上的方法
//相当于arguments.shift()
//[].shift 相当于 Array.prototype.shift
//取到参数中传入的对象(第一个参数为函数对象)
let constructor=[].shift.call(arguments);
//obj.__proto__ 与 Object.prototype 是一样的,都是指的Object类型的原型
obj.__proto__=constructor.prototype; //将传入对象链接到对象类型(obj)的原型
let result=constructor.apply(obj,arguments); //将obj的this绑定到传入对象上并返回新对象
return typeof result==='object'?result:obj //如果该函数没有返回值,则返回新对象,否则返回该函数的返回值
}
function People(name,age) {
this.name = name
this.age = age
}
let peo = createNew2(People,'Bob',22)
console.log(peo.name)
console.log(peo.age)
实现instanceOf
核心思想:判断左边实例的原型链上是否存在右边构造函数的prototype属性
function instanceOf(left,right) {
let lefttype=left.__proto__;
let righttype=right.prototype;
while(true){
if(righttype===null){
return false;
}
if(lefttype===righttype){
return true
}
lefttype=lefttype.__proto_
}
}
let arr=[]
console.log(instanceOf(arr,Array))
实现 Object.create
Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。
Object.create2 = function(proto, propertyObject = undefined) {
if (typeof proto !== 'object' && typeof proto !== 'function') {
throw new TypeError('Object prototype may only be an Object or null.')
if (propertyObject == null) {
new TypeError('Cannot convert undefined or null to object')
}
function F() {}
F.prototype = proto
const obj = new F()
if (propertyObject != undefined) {
Object.defineProperties(obj, propertyObject)
}
if (proto === null) {
// 创建一个没有原型对象的对象,Object.create(null)
obj.__proto__ = null
}
return obj
}
实现 Object.assign
Object.assign2 = function(target, ...source) {
if (target == null) {
throw new TypeError('Cannot convert undefined or null to object')
}
let ret = Object(target)
source.forEach(function(obj) {
if (obj != null) {
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
ret[key] = obj[key]
}
}
}
})
return ret
}
实现对象属性的get、set方法
function Person(){ }
Person.prototype.name="admin";
Person.prototype.getName=function(){
return this.name;
};
Person.prototype.setName=function(val){
this.name=val;
};
var per=new Person();
console.log(per.name)
或者(可链式调用)
function Person(){
this.value=1;
this.set=function(value){
this.value = value;
return this;
}
this.get=function(){
return this.value;
}
};
var person = new Person();
console.log(person.set(100).get()) // 100
继承
原型链继承
function Animal() {
this.colors = ['black', 'white']
}
Animal.prototype.getColor = function() {
return this.colors
}
function Dog() {}
Dog.prototype = new Animal()
let dog1 = new Dog()
dog1.colors.push('brown')
let dog2 = new Dog()
console.log(dog2.colors) // ['black', 'white', 'brown']
原型链继承存在的问题:
- 问题1:原型中包含的引用类型属性将被所有实例共享;
- 问题2:子类在实例化的时候不能给父类构造函数传参;
构造函数实现继承
function Animal(name) {
this.name = name
this.getName = function() {
return this.name
}
}
function Dog(name) {
Animal.call(this, name)
}
Dog.prototype = new Animal()
借用构造函数实现继承解决了原型链继承的 2 个问题:引用类型共享问题以及传参问题。但是由于方法必须定义在构造函数中,所以会导致每次创建子类实例都会创建一遍方法。
组合继承
组合继承结合了原型链和盗用构造函数,将两者的优点集中了起来。基本的思路是使用原型链继承原型上的属性和方法,而通过盗用构造函数继承实例属性。这样既可以把方法定义在原型上以实现重用,又可以让每个实例都有自己的属性。
function Animal(name) {
this.name = name
this.colors = ['black', 'white']
}
Animal.prototype.getName = function() {
return this.name
}
function Dog(name, age) {
Animal.call(this, name)
this.age = age
}
Dog.prototype = new Animal()
Dog.prototype.constructor = Dog
let dog1 = new Dog('奶昔', 2)
dog1.colors.push('brown')
let dog2 = new Dog('哈赤', 1)
console.log(dog2)
// { name: "哈赤", colors: ["black", "white"], age: 1 }
寄生式组合继承
组合继承已经相对完善了,但还是存在问题,它的问题就是调用了 2 次父类构造函数,第一次是在 new Animal(),第二次是在 Animal.call() 这里。
所以解决方案就是不直接调用父类构造函数给子类原型赋值,而是通过创建空函数 F 获取父类原型的副本。
function object(o) {
function F() {}
F.prototype = o
return new F()
}
function inheritPrototype(child, parent) {
let prototype = object(parent.prototype)
prototype.constructor = child
child.prototype = prototype
}
inheritPrototype(Dog, Animal)
class 实现继承
class Animal {
constructor(name) {
this.name = name
}
getName() {
return this.name
}
}
class Dog extends Animal {
constructor(name, age) {
super(name)
this.age = age
}
}
递归实现深拷贝
完整版:考虑各种数据类型
// 定义一个深拷贝函数 接收目标target参数
function deepClone(target) {
// 定义一个变量
let result;
// 如果当前需要深拷贝的是一个对象的话
if (typeof target === "object") {
// 如果是一个数组的话
if (Array.isArray(target)) {
result = []; // 将result赋值为一个数组,并且执行遍历
for (let i in target) {
// 递归克隆数组中的每一项
result.push(deepClone(target[i]));
}
// 判断如果当前的值是null的话;直接赋值为null
} else if (target === null) {
result = null;
// 判断如果当前的值是一个RegExp对象的话,直接赋值
} else if (target.constructor === RegExp) {
result = target;
} else if (Object.prototype.toString.call(target) === "[object Date]") {
result = new Date(target);
} else if (target instanceof Function) {
result = function () {
return target.apply(this, arguments);
};
} else {
// 否则是普通对象,直接for in循环,递归赋值对象的所有值
result = {};
for (let i in target) {
result[i] = deepClone(target[i]);
}
}
// 如果不是对象的话,就是基本数据类型,那么直接赋值
} else {
result = target;
}
// 返回最终结果
return result;
}
简单版:拷贝数组和对象
function deepClone(obj) {
let res = obj instanceof Array ? [] : {};
for (let i in obj) {
if (obj.hasOwnProperty(i)) {
res[i] = typeof obj[i] === "object" ? deepClone(obj[i]) : obj[i];
}
}
return res;
}