装饰器语法

简介: 利用typeof判定类型的取值范围是:'undefined' /'boolean' /'string' /'number' /'object' /'function' /'symbol'在JavaScript内部使用typeof判断类型依据的是二进制,根据变量的机器码低位1-3位存储其类型信息,有如下规则:

typeof


利用typeof判定类型的取值范围是:'undefined' /'boolean' /'string' /'number' /'object' /'function' /'symbol'

在JavaScript内部使用typeof判断类型依据的是二进制,根据变量的机器码低位1-3位存储其类型信息,有如下规则:

  • 000:对象
  • 010:浮点数
  • 100:字符串
  • 110:布尔
  • 1:整数

这里就引出了JavaScript的经典bug typeof null === 'object' // true

因为null的机器码为全0,那自然前三位也是0,所以这里会误判null的类型为object


instanceof


instanceof用来判断实例是否属于某种类型,或者实例的祖先属不属于某种类型,翻译一下就是只要右边变量的 prototype 在左边变量的原型链上即可,大致过程如下

function intanceofSelf(son, parent) {
  if(son === null || parent === null) {
    return false
  }
  let proto = son.__proto__
  while(1) {
    if(proto === parent.prototype) {
      return true
    }
    if(proto === null) {
      return false
    }
    proto = proto.__proto__
  }
}
console.log(intanceofSelf([], Array));
console.log([] instanceof Array);
复制代码

执行结果如下

1682521978(1).png

贴一张图来回忆一下原型

1682522006(1).png


new


new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象类型之一

来看一下真正的new操作之后是什么效果

1682522030(1).png

我们班来简单梳理一下new操作符所做的事情:

  1. 创建一个对象
  2. 将构造函数的原型挂载到新对象上
  3. 执行构造函数
  4. 返回新对象

大体功能已经分析出来了,开始代码实现,由于我们无法实现关键字的形式,我们就以工厂函数的形式来实现new操作

function createNewObj(constructor) {
  const args = Array.prototype.slice.call(arguments, 1)
  const obj = {}
  obj.__proto__ = constructor.prototype
  constructor.apply(obj, args)
  return obj
}
function Person(name, age) {
  this.name = name
  this.age = age
}
Person.prototype.getAge = function () {
  return this.age
}
let p = createNewObj(Person, 'king', 17)
console.log(p)
复制代码

来看一下执行结果,与原生new的效果相同,大功告成

1682522056(1).png


call


call是再Function原型上的方法,它的作用是改变函数执行时的this指向,也就是this绑定规则中的显示绑定

先来看一下使用示例,理解了使用方法才能去实现它

var foo = {
    value: 1
};
function bar() {
    console.log(this.value);
}
bar.call(foo); // 1
复制代码

实现思路:

  1. 将调用方法添加到指定的this上
  2. 执行方法
  3. 删除指定this上的方法

PS:这里注意call的this指向可以传null,当传值是null的时候,this指向window对象

Function.prototype.callSelf = function(ctx) {
  ctx = ctx || window
  const args = Array.from(arguments)
  args.shift()
  ctx.fun = this
  ctx.fun(...args)
  delete ctx.fun
}
const bar = {
  age: 18
}
var age = 17
function foo(name) {
  console.log(name)
  console.log(this.age)
}
foo.callSelf(bar, '张三')
foo.callSelf(null, '张三')
foo('张三')
复制代码

来看一下运行结果(不可以在node环境运行,node环境没有window)

1682522081(1).png

可以看到传值为null的时候也实现了绑定为window

PS:这只是原理上实现,call在ES3就已经实现了,我在代码中使用了多个ES6的方法


apply


apply的作用于call一样,唯一的区别在于二者接收的参数,call的参数是this指向+函数参数,apply的参数是this指向+函数参数组成的数组,如foo.call(bar, arg1, arg2)foo.apply(bar, [arg1, arg2])

那么这里也不再细说apply的原理了,直接上代码

Function.prototype.applySelf = function(ctx, args) {
  ctx = ctx || window
  ctx.fun = this
  ctx.fun(...args)
  delete ctx.fun
}
const bar = {
  age: 18
}
var age = 17
function foo(name) {
  console.log(name)
  console.log(this.age)
}
foo.applySelf(bar, ['张三'])
foo.applySelf(null, ['张三'])
foo('张三')
复制代码

运行结果

1682522109(1).png


bind


bind与apply和call作用大抵相同,区别在于bind返回的是一个可执行函数,看一下MDN上bind的介绍

bind() 方法会创建一个新函数。当这个新函数被调用时,bind() 的第一个参数将作为它运行时的 this,之后的一序列参数将会在传递的实参前传入作为它的参数。

一个绑定函数也能使用new操作符创建对象:这种行为就像把原函数当成构造器。提供的 this 值被忽略,同时调用时的参数被提供给模拟函数。

那也就是说,bind不再只是this指向的改变,而且要兼顾原型等因素,我们一步一步来

先来实现返回指定this的函数

Function.prototype.bindSelf = function (ctx) {
  var _this = this
  return function () {
    return _this.apply(ctx)
  }
}
var bar = {
  age: 18
}
var age = 19
function foo() {
  console.log(this.age)
}
const f = foo.bindSelf(bar)
f()
foo()
复制代码

不要忘了还可以接受参数,这里不再是简单的参数展开,他还可以分批传入(柯里化),如下

1682522129(1).png

当然,也不需要太过担心,其实实现也很简单,从两个函数作用域的arguments中提取即可

Function.prototype.bindSelf = function(ctx) {
  var _this = this
  var args = Array.prototype.slice.call(arguments, 1)
  return function() {
    _this.apply(ctx, args.concat(Array.from(arguments)))
  }
}
var bar = {
  value: 1
}
function foo(name, age) {
  console.log(name)
  console.log(age)
  console.log(this.value)
}
const f = foo.bindSelf(bar, 'king')
f(18)
复制代码

传参的问题解决了,接下来就是最头疼的构造器效果了,我们先来new一下试试,看看缺了什么

const f = foo.bindSelf(bar, 'king')
const o = new f(17)
console.log(o)
// 17
// 1
// {}
const f2 = foo.bind(bar, 'king')
const o2 = new f2(17)
console.log(o2)
// 17
// undefined
// foo {}
//      foo的实例对象
复制代码

如果你从上面看下来你会知道在执行new操作符的时候会将this指向要创建出来的对象实例,所以原生bind返回的函数在实例化时会找不到value,而我们自定义的bind在执行new操作符的时候this仍然指向bar对象,而且实例的原型链也有问题,所以会分析不出对象实例的类型

知道了问题所在我们就开始继续优化

Function.prototype.bindSelf = function(ctx) {
  var _this = this
  console.log(_this);
  var args = Array.prototype.slice.call(arguments, 1)
  var midFun = function() {
    _this.apply(this instanceof midFun ? this : ctx, args.concat(Array.from(arguments)))
  }
  midFun.prototype = this.prototype
  return midFun
}
复制代码

这里我们通过instanceof来判断当前操作是new还是作为普通函数执行。当执行new操作时,this指向对象实例,instanceof的结果为true,保持this指向对象实例;否则就是作为普通函数执行,this指向一开始传进来的上下文。这一段要好好消化一下,比较绕。

再来执行上面的测试数据,大功告成

1682522155(1).png

相关文章
|
3月前
|
测试技术 Python
装饰器
【8月更文挑战第1天】
20 2
|
6月前
|
JavaScript
03_装饰器
03_装饰器
65 1
|
6月前
22_自定义装饰器
22_自定义装饰器
116 0
|
6月前
|
自然语言处理 Java 编译器
【译】PEP 318--函数和方法的装饰器
【译】PEP 318--函数和方法的装饰器
44 0
|
6月前
|
数据安全/隐私保护 Python
解释装饰器(decorator)的功能和用法。
解释装饰器(decorator)的功能和用法。
52 1
|
JavaScript 开发者 索引
TypeScript-参数装饰器
TypeScript-参数装饰器
61 0
|
JavaScript 前端开发 索引
装饰器语法
@decorator 装饰器是 es7 更新的提案,是一种与类相关的语法,用来注释或修改类和类的方法,是在装饰器模式的基础上产生的。装饰器是过去几年中js最大的成就之一,已是ES7的标准特性之一。
107 0
|
存储 缓存 关系型数据库
Python装饰器1-闭包与函数装饰器
闭包与函数装饰器:被装饰函数不带参数、被装饰函数带参数、装饰器带参数,装饰器的调用
Python装饰器1-闭包与函数装饰器
|
JSON 数据格式
装饰器的实际应用
使用装饰器模式改造slf4j打印json格式日志
762 0
装饰器的实际应用
|
缓存 测试技术 Python
【Python函数式编程】——装饰器
装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值 也是一个函数对象。 它经常用于有以下场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景,装饰器是解决这类问题的绝佳设计。
152 0
【Python函数式编程】——装饰器