目录
一, 高级函数
1-1 安全的类型检测
1-2 作用域安全的构造函数
1-3 惰性载入函数
1-4 函数绑定
1-5 函数柯里化
1-6 反函数柯里化
一, 高级函数
1-1 安全的类型检测
想到类型检测, 那么脑海里第一反应应该就是在 Javascript 的世界中到底有哪些类型 (这真的是一个非常古老的问题了)
我们大致分为 2 类: 基本类型 和 引用类型
其中 基本类型 包括了: string,number,bool,undefined,null
其中 引用类型 包括了: Array,Function,Object
那我们用 type 和 instanceof 分别来看下这几种数据类型判定返回的内容 为什么说 利用 type 和 instanceof 是不安全的类型检测
- const str = 'test'
- const num = 12
- const bool = false
- const unde = undefined
- const nulls = null
- const Array = [1,2,3,4]
- const Object = {name: 'zzz'}
- const checkType = (type) => {
- return typeof(type)
- }
- // 使用 type 来判断
- console.log(checkType(str)) // string
- console.log(checkType(num)) // number
- console.log(checkType(bool)) // boolean
- console.log(checkType(unde)) // undefined
- console.log(checkType(nulls)) // object
- console.log(checkType(Array)) // object
- console.log(checkType(Object)) // object
- // 很显然 null,Array,Object 返回的都是 object 不够安全 ( bug 点 )
- // 用 instanceof 来判断
- const checkInstance = (type) => {
- return type instanceof String
- }
- console.log(checkInstance(str)) // 是 false 这是为什么呢?
- // 那么我们就需要来介绍下 instanceof 的原理了.
1-1-1 instanceof 的原理
instanceof 的的功能实现是 前者是否为后者的实例 , 具体的代码就是: eg:
- let res = a instanceof A
- // a 是 A 的实例
- // A 是 a 的构造函数
- // 同时 a 的 __proto__ == A 的 prototype 那么 a instanceof A == true 否则就等于 false
其中 有几个关键的点 如下:
关于 constrcutor ,proto ,prototype, 原型对象 这四个点的理解.
推荐一篇好文章吧 prototype,proto,constructor 的三角关系
回到上面 a.__proto__ 指向的就是 a 的原型对象
A.prototype 指向的是 实例对象 的 原型对象
- var Foo = function() {
- this.setName = (name) => {
- this.name = name
- }
- }
- var foo = new Foo
Foo.prototype 指向 => 原型对象 (理解为公共对象)
// 通过同一个构造函数实例化的多个对象具有相同的原型对象. 经常使用原型对象来实现继承
Foo.prototype.constructor 指向 => 构造函数本身 (Foo)
foo.__proto__ 指向 => 原型对象 (理解为公共对象)
foo.constructor 指向 => 构造函数 (Foo)
1-2 作用域安全的构造函数
在全局作用域内调用函数构造函数, 由于没有使用 new, 导致在全局作用域添加冗余的属性
- function Person(name,job) {
- this.name = name
- this.job = job
- }
- // 假如为使用 New 操作
- var person = Person('zhangsan','sell')
- console.log(window.name, window.job) // zhangsan sell
- console.log(person.name, person.job) // VM76:11 Uncaught TypeErrorr
这个问题是由 this 对象的晚绑定造成的 因此, 需要在函数里面确认 this 对象是正确类型的实例:
- function Person(name){
- if(this instanceof Person){
- this.name = 'zhang';
- } else {
- return new Person(name)
- }
- }
- var person = Person('zhang')
- console.log(window.name) // ''
- console.log(person.name) // zhang
1-3 惰性载入函数
惰性载入表示函数执行的分支会在函数第一次调用的时候执行, 在第一次调用过程中, 该函数会被覆盖为另一个按照合适方式执行的函数, 这样任何对原函数的调用就不用再经过执行的分支去进行判断了.(节约算力)
1-3-1 应用场景
1, AJAX 在不同浏览器下兼容性 2, APP 内嵌 H5 不同环境下同一种功能方法, 写法不一样 3, H5 在不同平台下多处表现形式因为一个方法而展现的不一样.
1-3-2 注意的地方
1, 应用越频繁, 越能体现这种模式的优势所在 2, 固定不变, 一次判定, 在固定的应用环境中不会改变 3, 复杂的分支判断, 没有差异性, 不需要应用这种模式
- 1-3-3 Demo
- const getCurEnv = () => {
- // 当前环境为 chrome 浏览器环境
- return window.navigator.userAgent.toLowerCase().match(/chrome/i) !== null
- }
- const Person = function(name) {
- this.name = name
- }
- const http = {
- created: function() {
- if (getCurEnv()) {
- console.log(this)
- this.created = function() {
- console.log('test1')
- return new Person('zhang1')
- }
- console.log('test2')
- return new Person('zhang2')
- } else {
- this.created = function() {
- console.log('test3')
- return new Person('zhang3')
- }
- }
- },
- Atest: ()=> {
- console.log(this) // window {}
- },
- Ftest: function() {
- console.log(this) // http {}
- }
- }
- http.created() // test2 Person {name: "zhang2"}
- http.created() // test1 Person {name: "zhang1"}
- // 实际有效的 惰性载入函数 上面的 二个 方法返回的值 其实是一样的. 这样惰性载入函数 才是真实有效.
1-4 函数绑定
这个技巧常常和回调函数与事件处理一起使用, 以便在将函数作为变量传递的同时保留代码执行环境
很多 JavaScript 库实现了一个可以将函数绑定到指定环境的函数, 这个函数一般都叫做 bind(). 一个简单的 bind() 函数接受一个函数和一个环境, 并返回一个给的环境中调用给定函数的函数, 并且将所有参数原封不动传递过去. 这个函数返回的是一个闭包.
上面的语言描述总是很虚无飘渺, 我们来直接上 Demo:
- 1-4-1 Demo
- var obj1 = {
- name: 'zhang',
- getName: function() {
- console.log(arguments[0][2], 'obj1')
- return this.name
- }
- }
- var obj2 = {
- name: 'lisi',
- getName: function() {
- console.log(arguments, 'obj2')
- return this.name
- }
- }
- function Bind(fn, context) {
- return fn.call(context, arguments)
- }
- Bind(obj1.getName,obj2,'xxxxx')
- // Arguments [Arguments(3), callee: ƒ, Symbol(Symbol.iterator): ƒ] "obj1"
- // 'lisi'
- // 这里我们对于 arguments 的 理解和操作来说都是比较陌生, 那么下面 我们再来介绍下
- // arguments 具体是什么.
- 1-4-2 arguments
类数组 (Array-like)
可以用下标访问每个元素
有 length 属性
arguments 的数据类型为 object
可以使用 for 和 for-in 方法
不具备 Array 原生方法
- Demo
- var test = function() {
- console.log(arguments)
- console.log(arguments[0])
- console.log(arguments.length)
- console.log(typeof arguments)
- for(var i = 0; i<arguments.length; i++) {
- var ele = arguments[i]
- console.log(ele)
- }
- for(x in arguments) {
- console.log(arguments[x])
- }
- // arguments.split(' ')
- // Uncaught TypeError: arguments.split is not a function
- }
- test(1,2,3,4)
- // Arguments(4) [1, 2, 3, 4, callee: ƒ, Symbol(Symbol.iterator): ƒ]
- // 1
- // 4
- // object
- // 1 2 3 4
- // 1 2 3 4
将类数组 转化为 数组
方法一 :
- var test = function() {
- console.log(arguments)
- var arrArg = Array.prototype.slice.call(arguments)
- console.log(arrArg)
- }
- test(1,2,3,4) // [1, 2, 3, 4]
方法二 :
- var test = function() {
- console.log(arguments)
- var arrArg = Array.from(arguments)
- console.log(arrArg)
- }
- test(1,2,3,4) // [1, 2, 3, 4]
1-4-3 ES5 中原生 bind() 方法 详解
文字解释起来还是比较吃力, 那么我们还是 showCode~ Demo:
- var obj = {
- a: 1,
- b: 2,
- getCount: function(c, d) {
- return this.a + this.b + c + d
- }
- }
- console.log(obj.getCount(3,4)) // 10
- window.a = window.b = 0
- var funcs = obj.getCount
- funcs(3,4) // 7
bind 是 function 的一个函数扩展方法, bind 以后代码重新绑定了 func 内部的 this 指向 (obj) 兼容 IE9 + Demo:
- var obj = {
- a: 1,
- b: 2,
- getCount: function(c, d) {
- return this.a + this.b + c + d
- }
- }
- console.log(obj.getCount(3,4)) // 10
- window.a = window.b = 100
- var funcs = obj.getCount.bind(obj)
- funcs(3,4) // 10
- // var funcs = obj.getCount.bind(window)
- // funcs(3,4) // 207
1-5 函数柯里化
又称部分求值. 柯里化其实本身是固定一个可以预期的参数, 并返回一个特定的函数, 处理批特定的需求. 这增加了函数的适用性, 但同时也降低了函数的适用范围. 文字的定义始终让人难以接受, 还是 showCode 吧 Demo: 假设你要写一个 记账的工具, 然后记录每天的数据, 最后统计整个星期的数据. how ?
- let weekCost = 0
- const cost = function(num) {
- weekCost += num
- }
- cost(100) // 100
- cost(200) // 300
- cost(300) // 600
这个时候每天都会进行一次 总账, 这个是我不想看到的, 因为不想每天都被这个总账看着心痛, 毕竟工资不够花是常态. 我就希望每个星期给我来一次总账刺激.
- const currying = function(fn) {
- let args = []
- return function() {
- if (arguments.length == 0) {
- return fn.apply(this, args)
- } else {
- let test = [].push.apply(args,arguments)
- // return fn.call(this, arguments)
- }
- }
- }
- const costs = (function() {
- let money = 0
- return function() {
- money = 0
- for(let i = 0; i<arguments.length; i++) {
- money += arguments[i]
- }
- return money
- }
- })()
- let cost = currying(costs)
- cost(100)
- cost(100)
- cost(100)
- cost(100)
- cost(100)
- console.log(cost()) // 500
- cost(100)
- cost(100)
- console.log(cost()) // 700
小结一:
上面的 dmeo 中, 当调用 cost() 时, 如果明确带上参数, 表明此时并不进行真正的求值计算, 而是把这些参数保存起来, 此时让 cost() 函数返回另外一个函数. 只有当我们以不带参数的形式执行 cost() 时, 才利用前面保存的所有参数, 真正开始求值计算. 这是一个具象的函数颗粒化的方法. 那么我们想把函数颗粒化抽象出来又需要怎么来概括呐?
下面的例子, 我们再来看下这个颗粒化!
- Demo
- const currying = function(fn) {
- let args = Array.prototype.slice.call(arguments, 1)
- return function() {
- let innerArgs = Array.prototype.slice.call(arguments)
- return fn.apply(this, args.concat(innerArgs))
- }
- }
- const add = function(n, m) {
- return n + m
- }
- var curriedAdd = currying(add, 3)
- console.log(curriedAdd(5)) // 8
小结二:
这个例子中, 通过颗粒化 创建已经设置好了一个或多个参数的函数.
后续还会有更多的例子, 来证明这个点.
注意
无论是柯里化函数或是绑定函数, 都会带来额外的开销, 所以不应滥用.
1-6 反函数柯里化
相反, 反柯里化的作用在与扩大函数的适用性, 使本来作为特定对象所拥有的功能的函数可以被任意对象所用.
核心:
通过 uncurrying 函数, 讲原本只能用于某个对象的方法, 扩展到更多的 对象可以引用. showCode:
- Function.prototype.uncurrying = function() {
- var that = this;
- return function() {
- return Function.prototype.call.apply(that, arguments);
- }
- }
- const test1 = {}
- const test2 = {}
- test1.sayHi = function () {
- return "Hello" + this.value +" "+[].slice.call(arguments);
- }
- test2.sayHiuncurrying = test1.sayHi.uncurrying()
- console.log(test2.sayHiuncurrying({value:'world'},"hahaha"));
- // Hello world hahaha
核心的代码已经展示出来了, 仔细的品读品读~
好了, 今天就先写到这里, 后面会继续完善对于 Demo 的解释, 不明白的可以留言讨论~
来源: https://www.cnblogs.com/erbingbing/p/9544420.html