原生JS基础巩固
原生js基础巩固
原型和原型链函数柯里化call/apply实现bind模拟实现最优继承instanceof实现
原型和原型链
提到js,原型和原型链是规避不过的,这之中存在几个容易混淆的概念,理解这些概念后,原型和原型链也就理解了。话不多说,先看一张原型链示意图。
图片来源
名词解释
构造函数
简单来说,构造函数本质就是一个函数而已,不同的是它可以用来创建对象(使用new操作符调用)
function Person(){}
这段代码中Person就是构造函数
实例
使用构造函数创建出来的对象
let p=new Person() 这段代码中的p就是构造函数Person的一个实例
原型对象
原型对象本质是一个对象,它的constructor属性指向创建该实例的构造函数。这里Person.prototype就是原型对象,Person.prototype.constructor属性指向构造函数Person。实例p可以通过__proto__属性访问其原型对象
查找机制
上述原型对象Person.prototype也有自己的原型对象,就是Object.prototype。换句话说,Person.prototype是Object.prototype的一个实例。自身没有的属性,会去原型上查找,这个查找过程就是原型链查找,上述蓝线就是一整条原型链。
验证
函数柯里化
柯里化是指的是将接受多个参数的函数变换成接受一个单一参数的函数,如果其他的参数是必要的,返回接受余下的参数且返回结果的新函数。
example
核心是所有参数的保留和第一个参数与后续参数的拆分
function add(...args) {
const iterator = (...rest) => {
args = [...rest, ...args];
return iterator;
};
iterator.toString = () => args.reduce((a, b) => a + b);
return iterator;
}
// 注意观察调用方式的区别
add(1, 2)(3)(4);//10
add(1)(2)(3)(4);//10
add(1,2,3,4);//10
call/apply实现
在实际开发中,call,和apply常用来改变this指向,这里简单模拟一下它们的内部实现
example
//先来个简单的例子感受一下
var stu = {
name:"tom",
age: 18,
};
function say() {
console.log(`my name is ${this.name},${this.age} years old`);
}
say.call(stu); // my name is tom,18 years old
功能点:
say函数中的this指向由window变为stu
say函数依然执行了
why?
仔细看看上述的两个功能点是如何实现的?其实很简单,只要
把say函数作为对象stu的一个内置方法
即可
//改写后如下
var stu = {
name: "tom",
age: 18,
say() {
console.log(`my name is ${this.name},${this.age} years old`);
}
};
//调用
stu.say() //my name is tom,18 years old
这样并没有结束,stu多了一个冗余方法,显然不合适,应该删除掉
call模拟实现
//call_v1
Function.prototype.call = function (context) {
//这里的context相当于上述stu
//这里的this指向目标调用函数,相当于上述say
//这里可以理解为为stu添加say方法
context.fn = this;
context.fn()//相当于执行stu.say()
delete context.fn//删除冗余方法,相当于delete stu.say
}
//call_v2
//下面加入传参处理,默认上下文处理和返回值处理
Function.prototype.call = function (context = window, ...args) {
//context=window 设置默认上下文为window
context.fn = this;
let result = context.fn(...args)//处理传参
delete context.fn
return result //返回值
}
apply模拟实现
这个和call基本一样,区别在于传参是数组形式
Function.prototype.apply= function (context = window, args=[]) {
//context=window 设置默认上下文为window
context.fn = this;
let result = context.fn(...args)//处理传参
delete context.fn
return result //返回值
}
bind模拟实现
bind的作用和call/apply类似,都可以改变this指向,不同的是,bind执行后返回的是一个函数,并不会自己调用。mdn上这样解释:bind()方法创建一个新的函数,在bind()被调用时,这个新函数的this被bind的第一个参数指定,其余的参数将作为新函数的参数供调用时使用。
example
var stu = {
name:"tom",
age: 18,
};
function say() {
console.log(`my name is ${this.name},${this.age} years old`);
}
var bindSay=say.bind(stu);
bindSay();// my name is tom,18 years old
bind模拟实现
/**
* 改变this指向
* 返回一个函数
* 可传参
* 柯里化
*/
Function.prototype.bind = function (context, ...args) {
if (typeof this !== "function") {
throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
}
let self = this
let MiddleFn = function () { }
let BindFn = function (...rest) {
return self.apply(this instanceof BindFn ? this : context, args.concat(rest));
}
MiddleFn.prototype = this.prototype
BindFn.prototype = new MiddleFn()
return BindFn;
}
最优继承
function Person(name) {
this.name = name;
}
Person.prototype.say=function(){
console.log(`My name is ${this.name}`)
}
function Student(name) {
Person.call(this, name)
}
// Object.create方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__
Student.prototype=Object.create(Person.prototype);
// 修正构造函数指向 Person=>Student
Student.prototype.constructor=Student;
var stu=new Student("tom");
stu.say()//My name is tom
instanceof实现
typeof 可用于判断基本类型值,instanceof操作符用于判断具体的引用类型
function _instanceof(left, right) {
//如果是基本数据类型,返回false
// typeof null 是object ,要处理这种情况
if (typeof (left) !== "object" || left == undefined) {
return false;
}
// 获取原型对象
// 举个例子:Object.getPrototypeOf([])===[].__proto__ true
let prototype = Object.getPrototypeOf(left);
while (true) {
//一直按着原型链查找 找到顶层还找不到,返回null
if (prototype === null) {
return false;
}
// 如果left right 原型对象一样 返回true
if (prototype == right.prototype) {
return true;
}
// 不满足条件的 继续查找
//一直到 Object.getPrototypeOf(Object.getPrototypeOf({})) null
prototype = Object.getPrototypeOf(prototype);
}
}