别真以为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函数就是函数中的三个函数,这 ...
随机推荐
- window.parent.document解决原生js或jQuery 实现父窗口的问题
做WEB前端开发的过程中,经常会有这样的需求,用户点击[编辑]按钮,弹出一个对话框,在里边修改相应的值,然后把修改后的值显示在原页面,最后点击保存. 用window.parent.document.g ...
- 近似推断(Approximate Inference)
1.变分推断(Variational Inference) 1.1.分解概率分布(Factorized distributions) 1.2.分解近似的性质(Properties of factori ...
- 第二百六十二节,Tornado框架-cookie
Tornado框架-cookie Cookie 是网站用来在客户端保存识别用户的一种小文件.一般来用库可以保存用户登 录信息.购物数据信息等一系列微小信息. self.set_cookie()方法,创 ...
- 敏捷软件开发实践-Release Process/Release Plan(转)
介绍: 因为我们的开发周期是迭代进行的,以Sprint为单位,我们每个 Sprint如何去和客户说我们的成果呢,那么我就需要Demo和release一些新功能,或者一些bug fixing.Demo我 ...
- 【转】Win32 创建控件风格不是Win XP解决方案
有时候我有在用Win32 API来向窗体上添加控件时,通过CreateWindow或CreateWindowEx创建出来的控件的风格不像XP风格,而是像Windows 2000的风格,界面很难看.注意 ...
- 登陆Oracle11g的企业管理器
本地:https://localhost:1158/em/ 如果远程:那么把localhost换成服务器IP
- 各种层次NET人应该知道些什么?
A.任何一个使用.NET的人 1.描述线程与进程的区别? 2.什么是Windows服务,它的生命周期与标准的EXE程序有什么不同 ? 3.Windows上的单个进程所能访问的最大内存量是多少?它与系统 ...
- android签名,制作key
签名具体步骤: Apk签名首先要有一个keystore的签名用的文件. keystore是由jdk自带的工具keytool生成的.具体生成方式参考一下: 开始->运行->cmd->c ...
- python2--升级python3
先安装开发工具包: yum -y group install "Development Tools" 安装Python的依赖包: yum -y install openssl-de ...
- HDU 5877 2016大连网络赛 Weak Pair(树状数组,线段树,动态开点,启发式合并,可持久化线段树)
Weak Pair Time Limit: 4000/2000 MS (Java/Others) Memory Limit: 262144/262144 K (Java/Others) Tota ...