由于模拟实现中有运用到隐式丢失, 所以在这还是先介绍一下。
隐式丢失是一种常见的this绑定问题, 是指: 被隐式绑定的函数会丢失掉绑定的对象, 而最终应用到默认绑定。说人话就是: 本来属于隐式绑定(obj.xxx
this指向obj)的情况最终却应用默认绑定(this指向全局对象)。
常见的隐式丢失情况1: 引用传递
var a = 'window'
function foo() {
console.log(this.a)
}
var obj = {
a: 'obj',
foo: foo
}
obj.foo() // 'obj' 此时 this => obj
var lose = obj.foo
lose() // 'window' 此时 this => window
常见的隐式丢失情况2: 作为回调函数被传入
var a = 'window'
function foo() {
console.log(this.a)
}
var obj = {
a: 'obj',
foo: foo
}
function lose(callback) {
callback()
}
lose(obj.foo) // 'window' 此时 this => window
// ================ 分割线 ===============
var t = 'window'
function bar() {
console.log(this.t)
}
setTimeout(bar, 1000) // 'window'
对于这个我总结的认为(不知对错): 在排除显式绑定后, 无论怎样做值传递,只要最后是被不带任何修饰的调用, 那么就会应用到默认绑定
进一步的得到整个实现的关键原理: 无论怎么做值传递, 最终调用的方式决定了this的指向
直观的描述硬绑定就是: 一旦给一个函数显式的指定完this之后无论以后怎么调用它, 它的this的指向将不会再被改变
硬绑定的实现解决了隐式丢失带来的问题, bind函数的实现利用就是硬绑定的原理
// 解决隐式丢失
var a = 'window'
function foo() {
console.log(this.a)
}
var obj = {
a: 'obj',
foo: foo
}
function lose(callback) {
callback()
}
lose(obj.foo) // 'window'
var fixTheProblem = obj.foo.bind(obj)
lose(fixTheProblem) // 'obj'
模拟实现call
// 模拟实现call
Function.prototype._call = function ($this, ...parms) { // ...parms此时是rest运算符, 用于接收所有传入的实参并返回一个含有这些实参的数组
/*
this将会指向调用_call方法的那个函数对象 this一定会是个函数
** 这一步十分关键 ** => 然后临时的将这个对象储存到我们指定的$this(context)对象中
*/
$this['caller'] = this
//$this['caller'](...parms)
// 这种写法会比上面那种写法清晰
$this.caller(...parms) // ...parms此时是spread运算符, 用于将数组中的元素解构出来给caller函数传入实参
/*
为了更清楚, 采用下面更明确的写法而不是注释掉的
1. $this.caller是我们要改变this指向的原函数
2. 但是由于它现在是$this.caller调用, 应用的是隐式绑定的规则
3. 所以this成功指向$this
*/
delete $this['caller'] // 这是一个临时属性不能破坏人为绑定对象的原有结构, 所以用完之后需要删掉
}
模拟实现apply
// 模拟实现apply ** 与_call的实现几乎一致, 主要差别只在传参的方法/类型上 **
Function.prototype._apply = function ($this, parmsArr) { // 根据原版apply 第二个参数传入的是一个数组
$this['caller'] = this
$this['caller'](...parmsArr) // ...parmsArr此时是spread运算符, 用于将数组中的元素解构出来给caller函数传入实参
delete $this['caller']
}
既然_call与_apply之前的相似度(耦合度)这么高, 那我们可以进一步对它们(的相同代码)进行抽离
function interface4CallAndApply(caller, $this, parmsOrParmArr) {
$this['caller'] = caller
$this['caller'](...parmsOrParmArr)
delete $this['caller']
}
Function.prototype._call = function ($this, ...parms) {
var funcCaller = this
interface4CallAndApply(funcCaller, $this, parms)
}
Function.prototype._apply = function ($this, parmsArr) {
var funcCaller = this
interface4CallAndApply(funcCaller, $this, parmsArr)
}
一个我认为能够较好展示_call 和 _apply实现原理的例子
var myName = 'window'
var obj = {
myName: 'Fitz',
sayName() {
console.log(this.myName)
}
}
var foo = obj.sayName
var bar = {
myName: 'bar',
foo
}
bar.foo()
模拟实现bind
// 使用硬绑定原理模拟实现bind
Function.prototype._bind = function ($this, ...parms) {
$bindCaller = this // 保存调用_bind函数的对象 注意: 该对象是个函数
// 根据原生bind函数的返回值: 是一个函数
return function () { // 用rest运算符替代arguments去收集传入的实参
return $bindCaller._apply($this, parms)
}
}
一个能够展现硬绑定原理的例子
function hardBind(fn) {
var caller = this
var parms = [].slice.call(arguments, 1)
return function bound() {
parms = [...parms, ...arguments]
fn.apply(caller, parms) // apply可以接受一个伪数组而不必一定是数组
}
}
var myName = 'window'
function foo() {
console.log(this.myName)
}
var obj = {
myName: 'obj',
foo: foo,
hardBind: hardBind
}
// 正常情况下
foo() // 'window'
obj.foo() // 'obj'
var hb = hardBind(foo)
// 可以看到一旦硬绑定后无论最终怎么调用都不能改变this指向
hb() // 'window'
obj.hb = hb // 给obj添加该方法用于测试
obj.hb() // 'window'
// 在加深一下印象
var hb2 = obj.hardBind(foo)
hb2() // 'obj' // 这里调用this本该指向window
function interface4CallAndApply(caller, $this, parmsOrParmArr) {
$this['caller'] = caller
$this['caller'](...parmsOrParmArr)
delete $this['caller']
}
Function.prototype._call = function ($this, ...parms) {
var funcCaller = this
interface4CallAndApply(funcCaller, $this, parms)
}
Function.prototype._apply = function ($this, parmsArr) {
var funcCaller = this
interface4CallAndApply(funcCaller, $this, parmsArr)
}
Function.prototype._bind = function ($this, ...parms) {
$bindCaller = this
return function () {
return $bindCaller._apply($this, parms)
}
}
// ============ 测试 ===============
var foo = {
name: 'foo',
sayHello: function (a, b) {
console.log(`hello, get the parms => ${a} and ${b}`)
}
}
var bar = {
name: 'bar'
}
foo.sayHello._call(bar, 'Fitz', 'smart')
foo.sayHello._apply(bar, ['Fitz', 'smart'])
var baz = foo.sayHello._bind(bar, 'Fitz', 'smart')
baz()
var testHardBind = foo.sayHello._bind(bar, 'hard', 'bind')
testHardBind._call(Object.create(null)) // hello, get the parms => hard and bind 测试_bind的硬绑定
inline-blcok元素中间的空白符引起的间隙,处理方法总结包括:改变书写结构、使用font-size:0、使用margin负值、使用letter-spacing或word-spacing、丢失结束标签、W3C推荐 导航方法(兼容IE6等)、YUI的inline-block间隙处理等...
本文实例讲述了JS实现碰撞检测的方法。分享给大家供大家参考,具体如下:一个简单的碰撞检测例子,检测div1是否和div2发生碰撞,当div1碰到div2时,改变div2的颜色
css实现边竖条的多种方式:border、使用伪元素、外 box-shadow、内 box-shadow、drop-shadow、渐变 linearGradient、轮廓 outline、滚动条
记录下与Math有关的常用方法,如:求最大值、最小值等,或者是保留几位数啥的。本文主要介绍了JavaScript中Math对象的方法,具有一定的参考价值,下面跟着小编一起来看下吧
Split是切割字符串的一种方法,该方法主要用于把一个字符串分割成字符串数组。splice()方法向/从数组中添加/删除元素,然后返回被删除的元素组成的数组。slice()方法主要用于截取数组,并返回截取到的新数组。
第一种:获取的时间戳是把毫秒改成000显示,因为这种方式只精确到秒,第二种和第三种是获取了当前毫秒的时间戳。
在web开发中我们经常会遇到页面的一个二级联动技术,二级联动字面解释就是说我在选择一级select不同的option,下面的二级option的属性值在进行相应的变化。
经常会用到原生JS来写前端。。。但是原生JS的一些方法在适应各个浏览器的时候写法有的也不怎么一样的,一下的方法都是包裹在一个EventUtil对象里面的,直接采用对象字面量定义方法了
是通过 js 操作 textarea input 输入框,只能操作输入框,不能操作其它元素。所有的 复制/剪切/粘贴 都是要在选中输入框中的文本之后,才进行操作的。
async函数是Generator 函数的语法糖,async函数就是将 Generator 函数的星号(*)替换成async,将yield替换成await,仅此而已。async函数对Generator 函数的改进点有以下几点:
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!