别真以为JavaScript中func.call/apply/bind是万能的!
自从学会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是万能的!的更多相关文章
- 深入浅出:了解JavaScript中的call,apply,bind的差别
在 javascript之 this 关键字详解文章中,谈及了如下内容,做一个简单的回顾: 1.this对象的涵义就是指向当前对象中的属性和方法. 2.this指向的可变 ...
- javascript中的call(),apply(),bind()方法的区别
之前一直迷惑,记不住call(),apply(),bind()的区别.不知道如何使用,一直处于懵懂的状态.直到有一天面试被问到了这三个方法的区别,所以觉得很有必要总结一下. 如果有不全面的地方,后续再 ...
- Javascript中call,apply,bind的区别
一.探索call方法原理 Function.prototype.call = function(obj) { // 1.让fn中的this指向obj // eval(this.toString().r ...
- JavaScript中call、apply个人理解
JavaScript中call.apply个人理解 一句话即通俗的说:call.apply 是为了改变this的状态而存在的 }; } function personInfo(name,age){ t ...
- 说说 JavaScript中 call和apply
下面有关JavaScript中 call和apply的描述,错误的是? call与apply都属于Function.prototype的一个方法,所以每个function实例都有call.apply属 ...
- js中的call,apply,bind区别
在JavaScript中,call.apply和bind是Function对象自带的三个方法,这三个方法的主要作用是改变函数中的this指向. call.apply.bind方法的共同点和区别:app ...
- JavaScript学习(2)call&apply&bind&eval用法
javascript学习(2)call&apply&bind&eval用法 在javascript中存在这样几种特别有用的函数,能方便我们实现各种奇技淫巧.其中,call.bi ...
- Learning JavaScript with MDN (call, apply, bind)
Learning JavaScript with MDN (call, apply, bind) call, apply, bind Object.prototype.toString() 检测 js ...
- Javascript中call、apply、bind函数
javascript在函数创建的时候除了自己定义的参数外还会自动新增this和arguments两个参数 javascript中函数也是对象,call.apply.bind函数就是函数中的三个函数,这 ...
随机推荐
- Cookie js 操作
从事web开发也有些日子了,cookie 是个啥差不多能说明白,可是实际自己一上手操作就是得去搜索(你们懂的),结果被鄙视了...所以就写一篇博文做为自己的学习笔记,嘿嘿,博客的好处在此体现出来了. ...
- Linux curl 模拟form表单提交信息和文件
Linux curl 模拟form表单提交信息和文件 curl是一个命令行方式下传输数据的开源传输工具,支持多种协议:FTP.HTTP.HTTPS.IMAP.POP3.TELNET等,功能超级强大 ...
- dedecms中如何去掉文章页面的广告
在arcticle_arcticle.htm页面找到广告调用代码{dede:myad name='myad'/}全部去掉就好了,如果要换成自己的广告,就换广告位标识 myad 就可以了
- before伪类的超有用应用技巧——水平菜单竖线分隔符
方法一.li前面加before伪类 <!doctype html> <html dir="ltr" lang="zh-CN"> < ...
- 【BZOJ】1061: [Noi2008]志愿者招募(费用流+数学)
http://www.lydsy.com/JudgeOnline/problem.php?id=1061 好神的一题! 学会了一种建模方式: 当方程组内的任意变量都在其中两个方程出现且一正一负,可以建 ...
- ThinkPHP项目笔记之RBAC(权限相关视频讲解篇
感谢互联网,只要你愿意找,没有找不到的免费资源 网址:http://www.studyfox.cn/143.html
- jdk 配置时时区设置
在eclipse中的 Default VM Arguments:添加 -Duser.timezone=Aisa/Shanghai
- cookie绕过验证码并关联对话发送一个随笔草稿箱
先手动发送一个草稿,然后用fiddler取到body参数 代码: #coding:utf-8import requests login_url="https://passport.cnblo ...
- 【黑金原创教程】 FPGA那些事儿《概念篇》
简介一本讲述非软硬片上系统的书,另外还是低级建模的使用手册. 目录[黑金原创教程] FPGA那些事儿<概念篇>:File01 - 结构的玩笑[黑金原创教程] FPGA那些事儿<概念篇 ...
- 【BZOJ4563】[Haoi2016]放棋子 错排+高精度
[BZOJ4563][Haoi2016]放棋子 Description 给你一个N*N的矩阵,每行有一个障碍,数据保证任意两个障碍不在同一行,任意两个障碍不在同一列,要求你在这个矩阵上放N枚棋子(障碍 ...