自从学会call/apply/bind这三个方法后我就各种场合各种使用各种得心应手至今还没踩过什么坑,怎么用?说直白点就是我自己的对象没有某个方法但别人有,我就可以通过call/apply/bind去调用执行别人家的方法,不太懂具体用法的同学可移至MDN学习一下Function.prototype.call() Function.prototype.apply() Function.prototype.bind() ,本文不讲解使用,但是这三个方法并不是万能的,并不一定会执行你想要的那个函数,因为可能要看函数中上下文对象的某些属性特性值允许不允许改变,今天这个坑我就深深踩了一下,并探索了一番。之所以说并不一定是因为确实有部分类数组可以传入call/apply/bind且成功调用,比如arguments(虽然它是[object Arguments]类型的,但__proto__却指向Object.prototype),感兴趣的可以试试。

事件起因
在学习HTML5与类相关的扩充其中classList属性时候,我了解到所有元素有classList这个属性,并且这个属性是新集合DOMTokenList的实例,在DOMTokenList.prototype上有一个add(value)方法,用来将给定字符串值添加到DOMTokenList实例列表中,并且即时反应到文档页面。如果在列表中value值已存在,就不添加了。我手贱寻思着自己用JS代码把这个add方法大概实现一下(肯定没人家JS引擎实现的好,我就实现个思路),但就这一实现才发现的这个坑。
(1).先来看这个add方法JS引擎给它设置的属性特性值如何,是可写的:

(2).然后实现我们的add函数

Object.defineProperty(DOMTokenList.prototype, 'add', {
configureable : true,
enumerable : true,
writable : true,
value : function(value){
if([].indexOf.call(this, value)>=0) return;
//加到classList类数组中
[].push.call(this, value);
}
});

是不是感觉没什么错,我就是想把'classA'加入到DOMTokenList实例列表中,然而当测试后控制台会报错

分析探讨
这个报错内容为"类型错误:不能设置改变[object Object]的那个特性值只有getter的length属性"。意思就是[object Object]就是这里的DOMTokenList.prototype,你换成childNodes它也同样会给你提示出错。

这里push方法因为会改变数组的长度length,而且不止push,pop,unshift,shift的执行也会改变数组长度,经测试它们会报出同样的错。这里的DOMTokenList.prototype.length获取它的特性值为:

length的访问器属性中set特性被引擎设置为undefiend了,怪不得不能将value加到document.body.classList类数组中,因为引擎没有给你提供set的接口函数啊,只给你提供get特性函数返回length长度。而我们平常使用的这个不会报错是因为length属性的value值是可变的,注意这里其实是每个数组实例都有自己的length属性。

不过好在DOMTokenList.prototype的length属性configurable特性是true,意味着我可以自己写length的set函数。现在整个过程就是:当实现我自己的add函数,因为add函数中调用到push操作,当执行push操作时会更新DOMTokenList.prototype.length(自动调用set函数),我可以在set函数中执行相关处理,先拿console测试一下是不是这个流程

Object.defineProperty(DOMTokenList.prototype, 'length', {
set : function(){console.log('执行了')}
})


确实是的,而且现在不报错了。但是我现在做的只是皮毛,'classA'还没有加到document.body.classList中去呢,看看列表里还只是"document":

不知道push函数引擎是怎么实现的,这说明JS引擎好像就没执行push函数,此方法行不通...不清楚它为什么不执行push操作??

那还是乖乖转化为数组再处理吧。常见的是将类数组转化为真正的数组再做相关处理,方法挺多比如Array.from(),Array.prototype.slice.call()等等,不过这会改变原来类数组的类型啊,我就想问怎么样处理能给类数组添加项而不改变类数组的类型。数组实例的类型由它的__proto__决定,那就好办了,不过得先设置classList属性的一些特性项,JS引擎给的set是undefiend

最后重写下来为:

Object.defineProperty(DOMTokenList.prototype, 'add', {
configureable : true,
enumerable : true,
writable : true,
value : function(value){
if([].indexOf.call(this, value)>=0) return;
//加到classList类数组中
var newarr = Array.from(this);
newarr.push(value);
newarr.__proto__ = DOMTokenList.prototype;
document.body.classList = newarr;
}
}); Object.defineProperty(Element.prototype, 'classList', {
set: function(value){
console.log(value);
//这里怎么处理不同元素的classList值是JS引擎的事,我实在是不会了
}
})

不过这样写有点固定具体元素了继续重写为:

Object.defineProperty(DOMTokenList.prototype, 'add', {
configureable : true,
enumerable : true,
writable : true,
value : function(value, ele){
if([].indexOf.call(this, value)>=0) return;
//加到classList类数组中
var newarr = Array.from(this);
newarr.push(value);
newarr.__proto__ = DOMTokenList.prototype;
ele.classList = newarr;
}
}); Object.defineProperty(Element.prototype, 'classList', {
set: function(value){
console.log(value);
//这里怎么处理不同元素的classList值是JS引擎的事,我实在是不会了
}
}); //使用
document.body.classList.add('classA', document.body);//["classA"]

总结
受限于JS引擎中元素实例的某些属性是共享于其原型属性的属性值的set函数,虽然最后也没有全部实现add函数的功能,我是实在不知道JS引擎中怎么实现的set,get函数从而保证不同实例共享原型上同一属性而且还保证不同实例的该属性值不一样,这估计得看引擎源码了,心累...有知道的同学可以说一下思路吗??还有对于我上面的分析部分我有两点疑问:知道的大神求科普!!
(1).JS引擎好像就没执行push函数,此方法行不通...不清楚它为什么不执行push操作
(2).怎么样处理能给类数组添加项而不改变类数组的类型

别真以为JavaScript中func.call/apply/bind是万能的!的更多相关文章

  1. 深入浅出:了解JavaScript中的call,apply,bind的差别

     在 javascript之 this 关键字详解文章中,谈及了如下内容,做一个简单的回顾:         1.this对象的涵义就是指向当前对象中的属性和方法.       2.this指向的可变 ...

  2. javascript中的call(),apply(),bind()方法的区别

    之前一直迷惑,记不住call(),apply(),bind()的区别.不知道如何使用,一直处于懵懂的状态.直到有一天面试被问到了这三个方法的区别,所以觉得很有必要总结一下. 如果有不全面的地方,后续再 ...

  3. Javascript中call,apply,bind的区别

    一.探索call方法原理 Function.prototype.call = function(obj) { // 1.让fn中的this指向obj // eval(this.toString().r ...

  4. JavaScript中call、apply个人理解

    JavaScript中call.apply个人理解 一句话即通俗的说:call.apply 是为了改变this的状态而存在的 }; } function personInfo(name,age){ t ...

  5. 说说 JavaScript中 call和apply

    下面有关JavaScript中 call和apply的描述,错误的是? call与apply都属于Function.prototype的一个方法,所以每个function实例都有call.apply属 ...

  6. js中的call,apply,bind区别

    在JavaScript中,call.apply和bind是Function对象自带的三个方法,这三个方法的主要作用是改变函数中的this指向. call.apply.bind方法的共同点和区别:app ...

  7. JavaScript学习(2)call&apply&bind&eval用法

    javascript学习(2)call&apply&bind&eval用法 在javascript中存在这样几种特别有用的函数,能方便我们实现各种奇技淫巧.其中,call.bi ...

  8. Learning JavaScript with MDN (call, apply, bind)

    Learning JavaScript with MDN (call, apply, bind) call, apply, bind Object.prototype.toString() 检测 js ...

  9. Javascript中call、apply、bind函数

    javascript在函数创建的时候除了自己定义的参数外还会自动新增this和arguments两个参数 javascript中函数也是对象,call.apply.bind函数就是函数中的三个函数,这 ...

随机推荐

  1. adb shell error: more than one device and emulator

    adb shell error: more than one device and emulator 本文转载出处: http://blog.sina.com.cn/s/blog_7ffb8dd501 ...

  2. SSH三大框架的知识题

    Struts 谈谈你对Struts的理解. 答: 1.struts是一个按MVC模式设计的Web层框架,其实它就是一个大大的servlet,这个Servlet名为ActionServlet,或是Act ...

  3. 判断站点访问的终端类型(移动端还是pc端)的方法(转)

    要了解某个网站是在移动设备上打开的还是在pc web浏览器中打开的,我们可以有以下综合的几种方式来搞定: 通过判断Request.UserAgent中的具体信息来分析判断,因为UserAgent包含了 ...

  4. XAMPP phpmyadmin修改mysql密码

    我手动修改了mysql的root账户的密码,然后就访问不了phpmyadmin了. 解决方法: 打开xampp目录(默认的安装目录,如果修改,请找到xampp的安装目录),打开phpmyadmin的目 ...

  5. iOS开发中多线程基础

    耗时操作演练 代码演练 编写耗时方法 - (void)longOperation { for (int i = 0; i < 10000; ++i) { NSLog(@"%@ %d&q ...

  6. Hibernate_day04--课程安排_Hibernate查询方式_对象导航查询_OID查询

    Hibernate_day04 上节内容 今天内容 Hibernate查询方式 对象导航查询 OID查询 HQL查询 查询所有 条件查询 排序查询 分页查询 投影查询 聚集函数使用 QBC查询 查询所 ...

  7. Linux命令之乐--read

    read 命令从标准输入中读取一行,并把输入行的每个字段的值指定给 shell 变量 -p:后接屏幕输出的提示语句 -n:设定输入的字符个数,当达到指定的个数则自动退出,并将输入赋予给变量 -t:当输 ...

  8. ORA-00845 MEMORY_TARGET not supported on this system解决办法

    ORA-00845: MEMORY_TARGET not supported on this system报错解决 Oracle 11g数据库修改pfile参数后启动数据库报错ora-00845 SQ ...

  9. Excel单元格格式设置

    工作中遇到一些小问题: 例如办公自动化里的如何设置单元格格式 此格式分为两种:一种是样式上的格式 比如边框 行距字体等 第二种为数据格式: 比如每次我输入1000的话自动变红或者加粗字体 office ...

  10. 170327、Java微信支付中的扫码支付

    微信支付现在已经变得越来越流行了,随之也出现了很多以可以快速接入微信支付为噱头的产品,不过方便之余也使得我们做东西慢慢依赖第三方,丧失了独立思考的能力,这次打算分享下我之前开发过的微信支付. 一 H5 ...