继承进阶:如何实现 new、apply、call、bind 的底层逻辑
一、new 原理介绍
new 关键词的主要作用就是执行一个构造函数、返回一个实例对象,在 new 的过程中,根据构造函数的情况,来确定是否可以接受参数的传递。
- 创建一个新对象
- 将构造函数的作用域赋给新对象(this指向新对象)
- 执行构造函数中的代码(为这个新对象添加属性)
- 返回新对象
- new 关键词执行之后总是会返回一个对象,要么是实例对象,要么是 return 语句指定的对象
function Person() {
this.name = 'person'
}
let p = new Person()
console.log(p.name)
let p1 = Person()
console.log(p1)
console.log(name)
// JS代码在默认情况下this的指向是window
function Person1() {
this.name = 'person1'
return { name: 'return' }
}
let p2 = new Person1()
console.log(p2)
console.log(p2.name)
function Person2() {
this.name = 'person2'
return 'return'
}
let p3 = new Person2()
console.log(p3)
console.log(p3.name)
二、apply & call & bind 原理介绍
call、apply和bind是挂在Function对象上的三个方法,调用这三个方法的必须是一个函数
func.call(thisArg,param1,param2,...)
func.apply(thisArg,[param1,param2,...])
func.bind(thisArg,param1,param2,...)
-
其中 func 是要调用的函数,thisArg一般为this所指向的对象,后面的param1、2为函数 func 的多个参数,如果 func 不需要参数,则后面的 param1、2可以不写
-
这三个方法共有的、比较明显的作用就是,都可以改变函数func的this指向。call和apply的区别在于,传参的写法不同:apply的第2个参数为数组;call则是从第2个至第N个都是给func的传参;而bind和这两个(call、apply)又不同,bind虽然改变了func的this指向,但不是马上执行,而这两个(call、apply)是在改变了函数的this指向之后立马执行。
let a = {
name: 'jack',
getName: function (msg) {
return msg + this.name
},
}
let b = {
name: 'lily',
}
console.log(a.getName('hello~'))
console.log(a.getName.call(b, 'hi~'))
console.log(a.getName.apply(b, ['hi~']))
let name = a.getName.bind(b, 'hello~')
console.log(name())
三、方法的应用场景
1.判断数据类型
// 1. 判断数据类型
function getType(obj) {
let type = typeof obj
if (type !== 'object') {
return type
}
return Object.prototype.toString.call(obj).replace(/^$/, '$1')
}
2.类数组借用方法
// 2. 类数组借用方法
const arrayLike = {
0: 'java',
1: 'script',
length: 2,
}
Array.prototype.push.call(arrayLike, 'jack', 'lily')
console.log(typeof arrayLike)
console.log(arrayLike)
3.获取数组的最大/最小值
// 3. 获取数组的最大/最小值
let arr = [13, 6, 10, 11, 16]
const max = Math.max.apply(Math, arr)
const min = Math.min.apply(Math, arr)
console.log(max)
console.log(min)
// 减少一步展开数组
console.log(max === Math.max(...arr))
4.继承
// 4. 继承
function Parent() {
this.name = 'parent'
this.arr = [1, 2, 3]
}
Parent.prototype.getName = function () {
return this.name
}
function Child() {
Parent.call(this)
this.type = 'child'
}
Child.prototype = new Parent()
Child.prototype.constructor = Child
let child = new Child()
console.log(child.getName())
四、如何自己实现这些方法?
1.new 的实现
- 让实例可以访问到私有属性
- 让实例可以访问构造函数原型所在原型链上的属性
- 构造函数返回的最后结果是引用数据类型
function _new(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])
}
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) {
let context = context || window
context.fn = this
let result = eval('context.fn(...args)')
delete context.fn
return result
}
3.bind 的实现
bind 的实现思路基本和apply一样,但是在最后实现返回结果这里,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
}
| 方法/特征 | call | apply | bind |
|---|---|---|---|
| 方法参数 | 多个 | 单个数组 | 多个 |
| 方法功能 | 函数调用改变 this | 函数调用改变 this | 函数调用改变 this |
| 返回结果 | 直接执行的 | 直接执行 | 返回待执行函数 |
| 底层实现 | 通过 eval | 通过 eval | 间接调用 apply |