本文主要围绕着 call、apply、bind、instanceof、new 的模拟实现,除了会用,更需要对其有更深刻的理解。
call
call 修改 this 指向
- 实例
var name = 'window name'
var dog = {
name: 'dog name',
getDog: function () {
console.log(this.name, [...arguments])
}
}
var cat = {
name: 'cat name',
getCat: function () {
console.log(this.name, [...arguments])
}
}
cat.getCat.call(dog, 1, 3); // "dog name"
dog.getDog.call(cat, 4, 5, 6); // "cat name"
- 模拟实现
Function.prototype._call = function (context = window, ...args) {
if (typeof this !== 'function') {
throw new TypeError('Error')
}
const key = Symbol() // 为 context 创建唯一的属性 key, 防止与 context 内部本身属性冲突
// 已知 this = cat.getCat = function () {...}
// context[key] = this, 等价于下面
// var dog = {
// name: 'dog name',
// getDog: function () {
// console.log(this.name, [...arguments])
// },
// getCat: function () {
// console.log(this.name, [...arguments])
// }
// }
context[key] = this
// 下一步执行 context 新增实例[key] 新方法, 因为此事这个方法已经放到了 dog 身上,所以等价于 dog.getCat()
// dog 对象中 this 指向它本身,所以打印出 this.name = 'dog name'
// 在此需要对 this 指向有很深的理解,this 永远指向 最后调用 它的那个对象
const result = context[key](...args)
// 最后删除为 context 添加的新属性 key
delete context[key]
// 函数有可能会有返回值,也需要一并返回
return result
}
apply
apply 修改 this 指向,与 call 区别仅在于参数传递方式
- 实例
var name = 'window name'
var dog = {
name: 'dog name',
getDog: function () {
console.log(this.name, [...arguments])
}
}
var cat = {
name: 'cat name',
getCat: function () {
console.log(this.name, [...arguments])
}
}
cat.getCat.apply(this); // "window name"
cat.getCat.apply(dog, [1], 3); // "dog name" [1]
dog.getDog.apply(cat, [4, 5, 6]); // "cat name" [4, 5, 6]
- 模拟实现
function isArray(target) {
return '[object Array]' === Object.prototype.toString.call(target);
}
Function.prototype._apply = function (context = window, args) {
if (typeof this !== 'function') {
throw new TypeError('Error')
}
if (!isArray(args)){
throw new TypeError('Error')
return false
}
// 与 call 解析一致 区别仅在参数解析方式
const key = Symbol()
context[key] = this
const result = context[key](...args)
delete context[key]
return result
}
bind
bind 修改 this 指向,与 call、apply 区别在于,bind调用返回的是一个新函数
- 实例
var name = 'window name'
var dog = {
name: 'dog name',
getDog: function () {
console.log('getDog', this.name, [...arguments])
}
}
var cat = {
name: 'cat name',
getCat: function () {
console.log('getCat', this.name, [...arguments])
}
}
cat.getCat.bind(this)(); // "window name"
cat.getCat.bind(dog, 10)(1, 3, 4); // "dog name" [10, 1, 3, 4]
dog.getDog.bind(cat)([4, 5, 6], 7); // "cat name" [[4, 5, 6], 7]
var factory = dog.getDog.bind(cat, 1)
var fac = new factory()
fac.name = 'fac name'
console.log(fac) // { name : 'fac name'}
- 模拟实现
Function.prototype._bind = function (context = window, ...args) {
const _this = this
return function F() {
// 需要知道:bind 返回的是一个函数,函数可能被 new, 作为构造函数被调用,如上实例 new factory()
// 此事 this 失效,需要指向 fac 实例对象,
if (this instanceof F) {
return new _this(...args, ...arguments) //等价于 new dog.getDog()
}
return _this.apply(context, args.concat(...arguments))
}
}
原型 / 构造函数 / 实例
为方便下面对 new instanceof 理解,需要提前对原型链有深的理解和实例、构造函数、原型对象之间的关系的理解
// 创建对象的4种方式
var o1 = {name: 'o1'}
var o2 = new Object({name: '02'})
function O3(){
this.name = 'o3'
}
var o3 = new O3()
var P = {name: 'o4'}
var o4 = Object.create(P)
o4.__propto__ = P
// 实例:有 o1 o2 o3 o4
// 构造函数:有 O3 【任何一个函数只要被new使用了,就可称之为构造函数】
// 原型对象: O3.prototype
// 实例属性: o1.__proto__ o2.__proto__ o3.__proto__ o4.__proto__
// 实例 === 构造函数
console.log(o3.__proto__.constructor === O3)
// 构造函数 === 原型对象
console.log(O3.prototype.constructor === O3)
// 实例 === 原型对象
console.log(o3.__proto__ === O3.prototype)
// 只有实例对象才有__proto__ 构造函数也有__proto__
// 只有构造函数有prototype属性
// 原型链
// 原型链是由原型对象组成,每个对象都有 __proto__ 属性,
// 指向了创建该对象的构造函数的原型,__proto__ 将对象连接起来组成了原型链。
// 是一个用来 实现继承和共享属性 的有限的对象链
new
构造函数的实现创建对象
function myNew (context) {
var obj = {} // 创建空对象
obj.__proto__ = context.prototype // 设置原型链 //obj = Object.create(context.prototype)
var result = context.call(obj) // 通过 call 为空对象添加属性
return result instanceof Object ? result : obj //返回值是否为对象,如果是对象,就使用构造函数的返回值,否则返回创建的对象
}
function Animation () {
this.name = 'animation'
}
var cat = myNew(Animation)
console.log(cat)
instanceof
在看具体实现之前,你需要提前对原型链有深刻理解
- 模拟实现
function _instanceof (left, right) {
let leftValue = left.__proto__
let rightValue = right.prototype
while(true) {
if (leftValue === null) {
return false
}
if (leftValue === rightValue) {
return true
}
leftValue = leftValue.__proto__ // 通过实例属性一层层往上找
}
}
继承
call
子类无法继承父类的原型 prototype 的方法
function Animate(name) {
this.name = name
}
Animate.prototype.getName = function() {
return this.name
}
function Cat(food) {
this.food = food
Animate.call(this) // Animate prototype 方法调用不了
}
Cat.prototype.getFood = function () {
this.food = food
}
var cat = new Cat('first')
原型链
子类可以引用父类的实例属性,但是需要绑定的时候,传值,否则为undefined,灵活度不高, 不同实例中属性值改变会相互影响 原因:原型链的对象不同实例中是共用的既:cat1.proto.name = 'cat1' 一改都改
function Animate(name) {
this.name = name
}
Animate.prototype.getName = function() {
return this.name
}
function Cat(food, name) {
this.food = food
}
Cat.prototype = new Animate('cat');
Cat.prototype.constructor = Cat
Cat.prototype.getFood = function () {
return this.food
}
var cat1 = new Cat('first')
var cat2 = new Cat('cat2')
组合模式-寄生
function Animate(name) {
this.name = name
}
Animate.prototype.getName = function() {
return this.name
}
function Cat(food, name) {
this.food = food
Animate.call(this, name)
}
Cat.prototype = new Animate(); // 1
//Cat.prototype = Object.create(Animate.prototype) //2
Cat.prototype.constructor = Cat
Cat.prototype.getFood = function () {
return this.food
}
var cat = new Cat('first', 'cat')
ES6
class Animate {
constructor(foo) {
this.foo = foo
}
getFood() {
console.log(this.foo)
}
}
class Cat extends Animate {
constructor(foo, bar) {
super(foo)
this.bar = bar
}
}
工具
JSBin 模拟练习