JS核心原理
模块一 基石篇
继承进阶:如何实现new、apply、call、bind的底层逻辑
思考问题:
1、用什么样的思路可以new关键词?
2、apply、call、bind这三个方法之间有什么区别?
3、怎样实现一个apply或者call的方法?
new原理介绍
new关键词的主要作用:
执行一个构造函数、返回一个实例对象
根据构造函数的情况,来确定是否可以接受参数的传递
function Person() {
this.name = 'jack';
}
var p = new Person();
console.log(p.name); //jack
//new执行的操作:
//1、创建一个新对象
//2、将构造函数的作用域赋给新对象(this指向新对象)
//3、执行构造函数中的代码(为这个新对象添加属性)
//4、返回新对象
//无new的情况
function Person() {
this.name = 'jack';
}
var p = Person();
console.log(p); //undefined
console.log(name); //jack
console.log(p.name) //'name'of undefined
//有return的情况,return的是一个对象
function Person() {
this.name = 'jack';
return {age: 18}
}
var p = new Person();
console.log(p); //{age: 18}
console.log(name); //undefined
console.log(p.name) //18
//有return的情况,return的不是一个对象
function Person() {
this.name = 'jack';
return 'tom'
}
var p = new Person();
console.log(p); //{name: jack}
console.log(p.name) //jack
new 关键词执行之后总是返回一个对象,要么是实例对象,要么是return语句指定的对象
apply、call、bind原理介绍
apply、call、bind是挂在Function对象上的三个方法
调用这三个方法的必须是一个函数
//基本语法
func.call(thisArg,param1,param2,...)
func.apply(thisArg,[param1,param2,...])
func.bind(thisArg,param1,param2,...)
//总结:
//共性:改变函数func的this指向
//call、apply的区别在于传参的写法不同,在改变this指向后立马执行
//bind和以上两个的区别是:不会马上执行
let a = {
name: 'jack',
getName: function() {
return msg+this.name;
}
}
let b = {
name: 'lily'
}
console.log(a.getName('hello~')); //hellp~jack
console.log(a.getName.call(b,'hi~')); //hi~lily
console.log(a.getName.apply(b,'hi~')); //hi~lily
let name = a.getName.bind(b,'hello~');
console.log(name()); //hello~lily
apply、call、bind方法应用场景
下面几种应用场景的理念都是“借用”方法的思路
1、判断数据类型
用Object.prototype.toString几乎可以判断所有类型的数据
function getType(obj) {
let type = typeof obj;
if(type !== "object"){
return type;
}
return Object.prototype.toString.call(obj).replace(/^\[object(\s+)\]$/,'$1')
}
2、类数组的借用方法
类数组因为不是真正的数组,所以没有数组类型上自带的种种方法,可以利用一些方法去借用数组的方法
// 借用数组的push方法
var arrayLike = {
0: 'java',
1: 'script',
length: 2
}
Array.prototype.push.call(arrayLike,'jack','lily');
console.log(typeof arrayLike); //'object'
console.log(arrayLike);
//{0: 'java',1: 'script',2: 'jack',3:'lily',length:4}
3、获取数组的最大/最小值
用apply来实现数组中判断最大/最小值,apply直接传递数组作为调用方法的参数,也可以减少一步展开数组
let arr = [13,6,10,11,16]
const max = Math.max.apply(Math,arr);
const min = Math.min.apply(Math,arr);
console.log(max); //16
console.log(min); //6
4、继承
function Parent3() {
this.name = 'parent3';
this.play = [1,2,3];
}
Parent3.prototype.getName = function() {
return this.name;
}
function Child3() {
//第二次调用Parent3()
Parent3.call(this);
this.type = 'child3';
}
//第一次调用Parent3()
Child3.prototype = new Parent3();
//手动挂上构造器,指向自己的构造函数
Child3.prototype.constructor = Child3;
var s3 = new Child3();
var s4 = new Child4();
s3.push(4);
console.log(s3.play,s4.play); //不互相影响
console.log(s3.getName()); //正常输出'parent3'
console.log(s4.getName()); //正常输出'parent3'
//问题:parent3执行了两次,多构造一次,就是一次性能开销
如何自己实现这些方法
手写实现new、call、apply、bind
1、new的实现
new被调用后大致做了哪几件事情
1、让实例可以访问私有属性
2、让实例可以访问构造函数原型(constructor.prototype)所在原型链上的属性
3、构造函数返回的最后结果是引用数据类型
function(ctor,...args) {
if(typeof ctor!== 'function'){
throw 'ctor must be a function';
}
let obj = new Object();
obj._ _proto_ _ = Object.create(ctor.prototype);
let res = ctor.apply(obj, ...args);
let isObject = typeof res === 'object' && typeof res !==null;
let isFunction = typeof res === 'function';
return isObject || isFunction ? res:obj;
}
2、apply和call的实现
结合方法“借用”的原理
Function.prototype.call = function (context,...args) {
var context = context || window;
context.fn = this;
var result = eval('context.fn(...args)');
delete context.fn
return result;
}
Function.prototype.apply = function (context,args) {
var context = context || window;
context.fn = this;
var result = eval('context.fn(...args)');
delete context.fn
return result;
}
//这两个方法是直接返回执行结果,而bind方法是返回一个函数,因此这里直接用eval执行得到结果
3、bind的实现
bind的实现思路基本和apply一样,但是在最后实现返回结果这里,bind不需要直接执行,因此不再需要用eval,而是需要通过返回一个函数的方式将结果返回,之后再通过执行这个结果,得到想要的执行效果。
Function.prototype.bind= function (context,...args) {
if(typeof this !== 'function') {
throw new Error("this must be a function")
}
var self = this;
var fbound = function() {
self.apply(this instanceof self ? this : context, args.concat(Array.prototype.slice.call(arguments)));
}
if(this.prototype){
fbound.prototype = Object.create(this.prototype);
}
return fbound;
}
//返回的是一个函数
总结