目录javascript
想到类型检测,那么脑海里第一反应应该就是在 Javascript 的世界中到底有哪些类型(这真的是一个很是古老的问题了)html
咱们大体分为 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 的原理了。
instanceof 的的功能实现是 前者是否为后者的实例 , 具体的代码就是:
eg:java
let res = a instanceof A // a 是 A 的实例 // A 是 a 的构造函数 // 同时 a 的 __proto__ == A 的 prototype 那么 a instanceof A == true 不然就等于 false
其中 有几个关键的点 以下:git
推荐一篇好文章吧 prototype、proto、constructor 的三角关系github
A.prototype 指向的是 实例对象 的 原型对象chrome
var Foo = function() { this.setName = (name) => { this.name = name } } var foo = new Foo Foo.prototype 指向 => 原型对象(理解为公共对象) // 经过同一个构造函数实例化的多个对象具备相同的原型对象。常用原型对象来实现继承 Foo.prototype.constructor 指向 => 构造函数自己(Foo) foo.__proto__ 指向 => 原型对象(理解为公共对象) foo.constructor 指向 => 构造函数 (Foo)
在全局做用域内调用函数构造函数,因为没有使用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
惰性载入表示函数执行的分支会在函数第一次调用的时候执行,在第一次调用过程当中,该函数会被覆盖为另外一个按照合适方式执行的函数,这样任何对原函数的调用就不用再通过执行的分支去进行判断了。(节约算力)安全
一、 AJAX 在不一样浏览器下兼容性
二、 APP 内嵌 H5 不一样环境下同一种功能方法,写法不同
三、 H5 在不一样平台下多处表现形式由于一个方法而展示的不同。
一、应用越频繁,越能体现这种模式的优点所在
二、固定不变,一次断定,在固定的应用环境中不会改变
三、复杂的分支判断,没有差别性,不须要应用这种模式
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"} // 实际有效的 惰性载入函数 上面的 二个 方法返回的值 实际上是同样的。这样惰性载入函数 才是真实有效。
这个技巧经常和回调函数与事件处理一块儿使用,以便在将函数做为变量传递的同时保留代码执行环境
不少JavaScript库实现了一个能够将函数绑定到指定环境的函数,这个函数通常都叫作bind()。一个简单的bind()函数接受一个函数和一个环境,并返回一个给的环境中调用给定函数的函数,而且将全部参数原封不动传递过去。这个函数返回的是一个闭包。
上面的语言描述老是很虚无飘渺,咱们来直接上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 具体是什么。
类数组 (Array-like)
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]
文字解释起来仍是比较吃力,那么咱们仍是 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
又称部分求值。柯里化其实自己是固定一个能够预期的参数,并返回一个特定的函数,处理批特定的需求。
这增长了函数的适用性,但同时也下降了函数的适用范围。
文字的定义始终让人难以接受,仍是 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
小结二:
这个例子中,经过颗粒化 建立已经设置好了一个或多个参数的函数。 后续还会有更多的例子,来证实这个点。
注意
不管是柯里化函数或是绑定函数,都会带来额外的开销,因此不该滥用。
相反,反柯里化的做用在与扩大函数的适用性,使原本做为特定对象所拥有的功能的函数能够被任意对象所用.
经过 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 的解释,不明白的能够留言讨论~
GitHub 地址:(欢迎 star 、欢迎推荐 : )