深圳建站费用,做瞹瞹瞹视频网站,源码搭建app教程,营业推广方案怎么写目录
原型链相关
手写instanceof
实现一个_instance方法#xff0c;判断对象obj是否是target的实例 测试 手写new的过程
实现一个myNew方法#xff0c;接收一个构造函数以及构造函数的参数#xff0c;返回构造函数创建的实例对象
测试myNew方法
手写类的继承
ES6判断对象obj是否是target的实例 测试 手写new的过程
实现一个myNew方法接收一个构造函数以及构造函数的参数返回构造函数创建的实例对象
测试myNew方法
手写类的继承
ES6classextends实现继承
组合继承调用两次父类构造函数
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的实例 思路instanceof的原理是基于原型链的概念通过遍历对象的原型链检查原型链中的某个原型是否等于目标构造函数的 prototype 属性。如果找到匹配则返回 true否则返回 false。 instanceof使用场景我们通常用typeof判断基本类型、symbol、function。对于对象的具体类型通常用instanceof来判断比如判断Map、Set、Array、Date类型。 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) {//原型上找到了targetreturn true;}proto Object.getPrototypeOf(proto);// proto proto.__proto__;}return false;
} 可以通Object.getPrototypeOf()方法拿到对象/构造函数原型的原型 也可以使用__proto__两个下划线proto拿到原型 测试 对Array、Set、Map类型进行测试 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方法接收一个构造函数以及构造函数的参数返回构造函数创建的实例对象 思路 创建一个对象obj使用 constructor.prototype 作为其原型。使用 apply 方法改变构造函数 constructor 的 this 指向为新对象 obj并将 args 传递给 constructor。如果 constructor 返回一个对象则返回 constructor 返回的对象否则返回 newObj。 //第一个参数构造函数第二个通过...拿到的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方法中调用constructor方法需要显示通过call或apply改变this。让方法找调用的对象obj。 测试myNew方法 测试myNew方法打印person实例调用person方法 // 测试
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();手写类的继承 在过去JavaScript 中实现类的继承有多种方式包括原型链继承、构造函数继承、组合继承、原型式继承等。这些方式各有优缺点需要开发人员根据具体情况进行选择和使用。 但是随着 ES6 的普及和使用开发人员可以更多地关注使用 class 和 extends 实现继承而不必过多地关注其他继承方式的优缺点。这也是为什么说类的继承在 ES6 普及后越来越不重要的原因之一。 ES6classextends实现继承
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(); //自己的方法手写继承就是不用Class和extends通过原型构造函数实现继承。在红宝书里写到虽然ES6类表面实现了面向对象编程但是背包使用的仍然是原型和构造函数的概念。 组合继承调用两次父类构造函数 组合继承原型链盗用父类构造函数 即通过call方法将父类的构造函数在子类中执行一遍使得子类也获取同样属性。将子类的prototype指向父类构造函数的实例实现子类和父类的原型链继承。 组合继承有两点注意一是在子类的构造函数里通过call方法手动让父类的构造函数在子类里执行一遍。其次必须先将子类的prototype更改为Parent的实例在去给子类添加方法。否则子类的方法会被Parent覆盖掉。 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原型式继承 Object.create() 是 JavaScript 中用于创建一个新对象使用现有的对象来作为新创建对象的原型prototype。Object.create() 方法接受一个对象然后以此对象为原型返回一个新的对象。Object.create() 的第二个参数是一个可选的属性描述对象用于定制创建的对象。这个参数与 Object.defineProperties() 方法的第二个参数格式相同。 使用Object.create方法继承适合不需要单独创建构造函数但仍然需要再对象之间共享信息的场合。但是属性中包含的引用类型的值始终会在对象间共享跟使用原型模式是一样的。Object.create方法的实现了下面的过程 function object(o){function F(){}F.prototype o;return new F();
} let person {name: 三月的一天,age: 18,
};
let anotherPerson Object.create(person);console.log(anotherPerson.name);//输出三月的一天
console.log(anotherPerson.age);通过Objec.create实现继承。通过Object的第二个参数 修改name增加sex属性。 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的constructorChild.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);原型链断裂通常是由于重写原型对象而导致的。在JavaScript中每个函数都有一个prototype属性用于指向原型对象而原型对象中又有一个constructor属性指向该函数本身。 当我们重写一个函数的prototype时实际上是创建了一个新的对象这个新对象的constructor属性会指向新对象的构造函数而不再指向原来的函数。 上面通过Child.prototype Object.create(Parent.prototype);这行代码实际上是将Child.prototype指向了一个新的对象prototype而这个新对象的constructor属性指向的是Parent构造函数而不是Child构造函数。这就导致了原型链的断裂因为Child.prototype.constructor不再指向Child构造函数。 为了避免原型链断裂我们可以手动修复constructor属性确保它指向正确的构造函数如您之前提到的Child.prototype.constructor Child;这行代码所做的操作。 手写call/apply/bind方法 通常在调用函数时函数内部的 this 值是访问该函数的对象。使用函数原型上的 call方法/apply方法/bind方法你可以在调用现有函数时将任意值分配给 this而无需首先将函数附加到对象上作为属性。这样可以将一个对象的方法用作通用的实用函数。 使用场景说人话就是当你自己本身没有某个方法想要借用别的对象上的方法时可以使用call或apply来改变this的指向使得方法似乎是当前对象自己的方法。这种感觉就像是方法去找对象。所有call/apply/bind的左侧使用方是函数函数.call(谁调用我参数拿来)。 具体使用哪个方法取决于你的具体需求
使用call当你知道函数的参数是哪些并且想要按顺序传递它们时。使用apply当你知道函数的参数是一个数组并且想要以数组的形式传递这些参数时。使用bind当你需要一个新函数新函数的this指向和参数已经确定时或者当你需要动态地传递参数时。
手写思路 将函数this设为对象的属性执行函数处理入参删除这个属性 原生call、apply传入的this如果是值类型会被new Object如fn.call(abc) 手写call方法 举一个使用call方法的例子
var foo {value: 1
};function bar() {console.log(this.value);
}bar.call(foo); // 1可以看call 改变了 this 的指向指向到 foo然后bar 函数执行了。 一般来说带有this的是构造函数需要通过new构造函数拿到构造函数的实例对象然后通过对象访问方法。但是这里好像在执行foo.bar方法。可是foo又不是构造函数的实例怎么foo.bar进行方法调用换个思路将bar当成是对象的某个属性呢 那么模拟call的过程是不是可以假设将bar函数当成是foo的一个属性foo.属性对象访问属性 var foo {value: 1,fn: function bar() {console.log(this.value);},
};foo.fn();首先call方法思路将函数设为对象的属性通过对象.属性执行函数删除函数不能真正改变原对象的属性 如果不传入参数时设置默认指向window 普通版call方法
Function.prototype.myCall function (context window, ...args) {context.fn this;let result context.fn(...args);delete context.fn;return result;
}; 这样我们得到了一个简单的myCall方法。通过给对象context添加一个fn属性fn的值为函数。然后立刻执行函数。之后将context.fn删除但是有个问题如果context对象本身就有fn。这样不就破坏了原始对象的属性了吗怎么给新增的属性起一个不冲突的名字呢是不是可以用Symbol啊。 在JavaScript中Symbol是一种新的原始数据类型它的实例是唯一且不可变的。由于Symbol的值是唯一且不可变的所以不能通过点语法直接访问一个Symbol属性。这是因为Symbol属性不会出现在对象的属性枚举中例如使用for...in或Object.keys()这是符合语言设计的一部分。 为了访问一个Symbol属性可以使用中括号语法并将Symbol作为属性名。这是因为Symbol属性不会被覆盖或者更新因此可以确保在访问时不会发生冲突。 使用Symbol标识属性
Function.prototype.myCall function (context window, ...args) {let fn Symbol(); //将fn属性名称定义为Symbolcontext[fn] this; //通过[]访问Symbol类型变量将this当前函数赋给valuelet result context[fn](...args); //调用方法delete context[fn]; //删除属性fnreturn result;
}; 这样是不是差不多了。这里假设context是对象如果context是普通类型第二行就会报错。而原始call方法是不会报错的。这个时候要对基本类型进行转换。 处理非object类型绑定
//添加函数原型myCall方法
Function.prototype.myCall function (context window, ...args) {if (typeof context ! object) {//非object类型的手动转objectcontext new Object(context);}let fn Symbol(); //将fn属性名称定义为Symbolcontext[fn] this; //通过[]访问Symbol类型变量将this当前函数赋给valuelet result context[fn](...args); //调用方法delete context[fn]; //删除属性fnreturn result;
};
测试call方法 手写apply方法 apply和call的差异就是入参的区别 call()方法接收的是参数列表即每个参数都需要单独传递。apply()方法接收的是一个包含多个参数的数组即所有参数都放在一个数组中传递。 将call的手写入参改一下就好了 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方法 传入数组args成功解析 手写bind方法 bind()是JavaScript中函数的一个内置方法用于创建一个新的函数该函数在调用时this值会被绑定到传给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方法 根据bind的作用调用的函数fn.bind(调用对象obj,...部分参数1)生成一个新的函数boundFn。然后对这个新的boundFn函数传参调用它 boundFn(...剩余参数。 我们得到下面的bind方法。 将当前this保存给fn。因为当前不执行fn函数而是在boundFn函数内部执行。 myBind方法返回boundFn函数。boundFn这个函数内部还继续接收入参。将闭包内首次入参args与innerArgs合并作为fn全部的入参。 //myBind方法的调用形式let boundFn fn.myBind(obj,...args)
//fn是对象想要使用的函数
//fn是调用方法的对象args是bind时候传入的函数入参的部分或全部参数
Function.prototype.myBind function (context window, ...args) {const fn this; //这里的this是fnfunction boundFn(...innerArgs) {//创建一个新函数boundFnreturn fn.apply(context, args.concat(innerArgs)); //执行fnfn从闭包中的this获取所以this要提前存给fnfn的参数要将两次入参拼起来}return boundFn; //返回新函数 新函数执行boundFn(...innerArgs)
}; 定义一个内部函数 boundFn它接收任意数量的剩余参数 ...innerArgs。在 boundFn 函数内部使用 fn.apply() 方法调用原函数 fn并将 fn 函数的执行上下文设置为指定的 context 对象将原函数 fn 的参数 args 与新传入的参数 innerArgs 合并后作为入参传递给 fn。这样即使调用bind分两次传参底层还是将两个参数合并了。 返回 boundFn 函数这个函数可以在调用时传入参数并在指定的 context 对象上执行原函数 fn 由于函数还包括了构造函数。如果原始fn是个构造函数那么新的boundFn也是构造函数构造函数要通过new得到实例。这时fn实际执行的context就是 new boundFn后的对象也就是boundFn里的this。 用作构造函数boundFn处理 对boundFn函数内部执行fn方法的context做判断。如果boundFn用作构造函数那么fn里的context就是this。 myBind里出现了两个this。第一个this是fn函数实际执行的函数在闭包中被fn保留下来。 第二个this是bind返回的函数调用方也就是new后的对象。这两个this是不同的意思。 为什么用this instanceof boundFn 因为newObj new boundFn()得到instanceof可以判断实例是否通过构造函数生成的 //myBind方法的调用形式let boundFn fn.myBind(obj,...args)
//fn是对象想要使用的函数
//fn是调用方法的对象args是bind时候传入的函数入参的部分或全部参数
Function.prototype.myBind function (context window, ...args) {const fn this; //这里的this是fnfunction boundFn(...innerArgs) {//创建一个新函数boundFncontext this instanceof boundFn ? this : context;//如果boundFn被当做构造函数执行fn的对象就是当前的thisreturn fn.apply(context, args.concat(innerArgs)); //执行fnfn从闭包中的this获取所以this要提前存给fnfn的参数要将两次入参拼起来}return boundFn; //返回新函数 新函数执行boundFn(...innerArgs)
};这里构造函数还没考虑继承如果fn也是一个构造函数呢boundFn是不是还要处理原型链关系。原型继承是不是要用“子类原型Object.create(父类构造函数原型)’模板继承 考虑构造函数继承 创建新的函数跟new的过程差不多都要处理继承。将boundFn与fn建立原型链。这样新返回的函数就能跟fn执行一样的方法了。 //myBind方法的调用形式let boundFn fn.myBind(obj,...args)
//fn是对象想要使用的函数
//fn是调用方法的对象args是bind时候传入的函数入参的部分或全部参数
Function.prototype.myBind function (context window, ...args) {const fn this; //这里的this是fnfunction boundFn(...innerArgs) {//创建一个新函数boundFncontext this instanceof boundFn ? this : context; //如果boundFn被当做构造函数执行fn的对象就是当前的thisreturn fn.apply(context, args.concat(innerArgs)); //执行fnfn从闭包中的this获取所以this要提前存给fnfn的参数要将两次入参拼起来}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);测试结果bind后参数可以分开给统一执行Person方法