手撕代码-JavaScript

call

更改函数的 this 指向并执行函数并将结果返回。

Function.prototype.myCall = function(context) {
var context = context || window
context.fn = this
var args = []
for(var i = 1; i < arguments.length; i++) {
args.push('arguments[' + i + ']')
}
var result = eval('context.fn(' + args + ')')
delete context.fn
return result
}

分析:

  • 首先 context 作为可选参数,如果不传或传 null ,默认上下文为 window
  • 然后给上下文 context 添加方法 fnfn 就是要执行的函数,谁调用了 call() , fn 就是谁。 所以 context.fn = this
  • call 还可以传入多个参数作为调用的函数的参数,可以通过 arguments 对象获取函数的参数。
  • 最后调用函数保存返回值,删除给对象添加的 fn ,将结果返回。

核心就是,通过将函数添加到对象上,然后通过对象调用这个函数,从而使函数中的 this 指向这个对象。

更简洁的写法:

Function.prototype.myCall = function(context, ...rest) {
var context = context || window
context.fn = this
var result = context.fn(...rest)
delete context.fn
return result
}

apply

apply 实现与 call 类似,区别在于对参数的处理

Function.prototype.myApply = function(context, arr) {
var context = context || window
context.fn = this
var result
if(!arr) {
result = context.fn()
} else {
var args = []
for(var i = 0; i < arr.length; i++) {
args.push('arr[' + i + ']')
}
result = eval('context.fn(' + args + ')')
}
delete context.fn
return result
}

简洁:

Function.prototype.myApply = function(context, arr) {
var context = context || window
context.fn = this
var res = arr ? context.fn(...arr) : context.fn()
delete context.fn
return res
}

bind

创建并返回一个新的改变了 this 的函数。并在调用新函数时,会将在调用 bind 时给定的参数列表作为原函数的参数序列的前若干项。

举个例子:

function foo(name,age) {
console.log('name:' + name + 'age:' + age)
}
var f = foo.bind(null,'张三')
f(20) // name:张三age:20

实现:

Function.prototype.myBind = function(context) {
var self = this
var args = [].slice.call(arguments, 1)
function F() {
var args2 = [].slice.call(arguments)
var arglist = args.concat(args2)
return self.apply(this instanceof F ? this: context, arglist)
}
F.prototype = Object.create(self.prototype)
return F
}

  • bind 返回的是一个函数,在这个函数里面返回了 原函数.apply() 实现改变 this 。
  • 如果 bind 返回的新函数作为构造函数执行时(new 函数),this 不能改成传入的 context ,this 应该是声明的变量。
  • bind() 传入的除 context 的后续参数,先存起来,在调用由 bind 返回的新函数时 ,向 apply() 传入由原来存的参数和新传入的参数组成的数组(注意顺序)。

简洁:

Function.prototype.myBind = function(context, ...rest) {
var self = this
function F() {
return self.apply(this instanceof F ? this : context,rest.concat(...arguments))
}
F.prototype = Object.create(self.prototype)
return F
}

new

new 运算符创建了一个用户自定义类型的实例

在执行 new 构造函数() 时,发生了:

  1. 生成一个新对象
  2. 将对象的原型引用(__proto__)指向构造函数的原型
  3. 绑定 this 为该对象,执行构造函数中的内容
  4. 如果函数执行的返回值是对象,就返回这个对象,否则返回之前创建的新对象
function myNew() {
var constructor = [].shift.call(arguments)
var obj = Object.create(constructor.prototype)
var res = constructor.apply(obj, arguments)
return res instanceof Object ? res: obj
}

简洁:

function myNew(constructor, ...rest) {
let obj = Object.create(constructor.prototype)
let res = constructor.apply(obj, rest)
return res instanceof Object ? res : obj
}

instanceof

通常能正确判断对象的类型,原理是判断构造函数的原型对象是否能在对象的原型链上

function myInstanceof(obj, fn) {
let prototype = fn.prototype
let objProto = Object.getPrototypeOf(obj)
while(true) {
if(objProto == null)
return false
if(objProto === prototype)
return true
objProto = Object.getPrototypeOf(objProto)
}
}
  1. 获得构造函数的原型
  2. 获得实例对象的原型
  3. 判断构造函数的原型是否就是实例对象的原型,如果是就返回 true ,不是就让实例对象的原型指向自身的原型继续判断,直到实例对象的原型为 null,返回 false
© 2019 墨夜 All Rights Reserved.
Theme by hiero