在学习cocos点击事件的时候,产生了这样一个疑问:
this.node.on(cc.Node.EventType.MOUSE_UP, this.onMouseUp,this)
这个方法里面的参数,第一个是代表监听鼠标抬起时候,第二个是调用的方法名,那第三个是什么呢?之前一直是this.node这样用的。
start () {
this.node.on(cc.Node.EventType.MOUSE_UP, this.onMouseUp,this)
}
onMouseUp(){
cc.log(this);
}
我尝试输出这个this,得到这样的结果
ClickTest是我挂载在node上的脚本,this代表了这个脚本,所以之前想要拿到自己定义或者挂载的东西的时候就可以用this.XXX
在查阅资料后,发现要改变这个this的指向,就要接触到bind、apply、call这三个东西
call
我将脚本挂载在一个node上面,点击node就会调用一次call:
const {ccclass, property} = cc._decorator;
var tmp = {
name:'我的名字'
};
@ccclass
export default class ClickTest extends cc.Component {
start () {
//启动鼠标监听
this.node.on(cc.Node.EventType.MOUSE_UP, this.onMouseUp,this)
}
private person(){
cc.log(this.name);
}
onMouseUp(){
this.person.call(tmp);
}
//输出‘我的名字’
}
const {ccclass, property} = cc._decorator;
var tmp = {
name:'我的名字',
person:function(){
cc.log(this.name);
}
};
@ccclass
export default class ClickTest extends cc.Component {
start () {
this.node.on(cc.Node.EventType.MOUSE_UP, this.onMouseUp,this)
}
// private person(){
// cc.log(this.name);
// }
onMouseUp(){
tmp.person();
}
}
两次输出的结果都是 ‘我的名字’
这样用到的是this的隐式绑定,当函数person引用有上下文对象的时候,就会把函数person调用中的this,绑定到这个上下文对象tmp了。
简单来说,第二次能够输出‘我的名字’是因为tmp里面的person函数中出现this,this就会自动绑定到tmp上面。因为tmp直接执行了person函数,tmp是真正的调用位置,因此this直接指向了tmp
而第一种调用call输入的‘我的名字’是因为call的第一个参数代表了this的指向,call可以改变this的指向
为了更好的理解call的原理,我想模仿call写一个newCall:
因为call在构造函数的原型对象里面
要模仿写一个call就必须在原型对象里面添加新的和call一样的属性,尝试用不同的方式输出this:
var tmp = {
name:'我的名字',
};
function person(){
cc.log(this);
}
Function.prototype.newCall = function(obj){
cc.log(this)
}
@ccclass
export default class ClickTest extends cc.Component {
start () {
this.node.on(cc.Node.EventType.MOUSE_UP, this.onMouseUp,this)
}
onMouseUp(){
person();
cc.log(this)
person.newCall(tmp);
person.call(tmp)
}
}
结果如下:
1.person() 因为没有在类里面调用到它所有输出默认的window
2.cc.log(this) 是在类中调用的,所以this指向类,输出ClickTest
3.person.newCall(tmp) person直接执行了newCall函数,person是真正的调用位置,所以输出了person
4.person执行call之后,this要指向tmp,所以输出tmp对象。
那如何让newCall可以像call一样找到tmp呢?
既然原来的newCall里面this指向了person,但是没有执行person,那我们创建一个对象p,把person赋给p,p可以执行person不就好了
var tmp = {
name:'我的名字',
};
function person(){
cc.log(this.name);
}
Function.prototype.newCall = function(obj){
//cc.log(this)
obj.p = this;
obj.p();
delete obj.p;
}
@ccclass
export default class ClickTest extends cc.Component {
start () {
this.node.on(cc.Node.EventType.MOUSE_UP, this.onMouseUp,this)
}
onMouseUp(){
person.call(tmp);
person.newCall(tmp);
}
}
输出结果:
call的第一个参数是this的指向,后面还可以接受很多个参数,在newCall里面,可以把参数存在arguments内,然后用eval()函数将传入的字符串作为 JavaScript 代码进行执行:
var tmp = {
name:'我的名字',
};
function person(a,b,c,d){
cc.log(this.name);
cc.log(a,b,c,d);
}
Function.prototype.newCall = function(obj){
//cc.log(this)
obj.p = this;
var newArguments = [];
for(var i=1;i<arguments.length;i++){
newArguments.push('arguments[' + i + ']');
}
eval('obj.p('+newArguments+')');
delete obj.p;
}
@ccclass
export default class ClickTest extends cc.Component {
start () {
this.node.on(cc.Node.EventType.MOUSE_UP, this.onMouseUp,this)
}
onMouseUp(){
//person.apply(tmp);
person.newCall(tmp,'1a','2b','3c','4d');
}
}
结果:
apply
apply其实和call没有太大的区别,不同之处在于提供参数的方式。apply 使用参数数组而不是一组参数列表。apply 可以使用数组字面量(array literal),如 fun.apply(this, [‘eat’, ‘bananas’]),或数组对象, 如 fun.apply(this, new Array(‘eat’, ‘bananas’))。
所以写newApply的时候,就要传入第二个形参作为数组:
var tmp = {
name:'我的名字',
};
function person(a,b,c,d){
cc.log(this.name);
cc.log(a,b,c,d);
}
Function.prototype.newApply = function(obj,arr){
//cc.log(this)
obj.p = this;
if(!arr){
obj.p();
}else{
var newArguments = [];
for(var i=0;i<arr.length;i++){
newArguments.push('arr[' + i + ']');
}
eval('obj.p('+newArguments+')');
}
delete obj.p;
}
@ccclass
export default class ClickTest extends cc.Component {
start () {
this.node.on(cc.Node.EventType.MOUSE_UP, this.onMouseUp,this)
}
onMouseUp(){
//person.apply(tmp);
person.newApply(tmp,['1a','2b','3c','4d']);
}
}
结果:
Bind:
bind会返回一个函数,可以像call一样后面跟着很多参数
所以写newBind的话需要return function(){},而且要注意把传入的arguments对象的第一个数组元素切割,但是对象不能直接用数组的切割方法,需要利用call方法把slice切割方法赋给arguments对象,如下:
var tmp = {
name:'我的名字',
};
function person(){
cc.log(this.name);
}
Function.prototype.newBind = function(obj){
//this指向的是person函数
var that = this,
arr = Array.prototype.slice.call(arguments,1);
cc.log(arr);
return function(){
//里面其实就是实现call和apply的功能 要接收一个数组显然很麻烦,因此使用apply,在控制参数方面,还是apply会比较好用
that.apply(obj);
}
}
@ccclass
export default class ClickTest extends cc.Component {
start () {
this.node.on(cc.Node.EventType.MOUSE_UP, this.onMouseUp,this)
}
onMouseUp(){
//person.apply(tmp);
person.newBind(tmp,'1a','2a')();
}
}
那bind的第二个括号里面应该放的是什么呢?
在newBind里面其实有两个不同的arguments对象,把它们输出出来:
const {ccclass, property} = cc._decorator;
var tmp = {
name:'我的名字',
};
function person(test){
cc.log(this.name);
cc.log(test);
}
Function.prototype.newBind = function(obj){
//第一个
cc.log(arguments)
return function(){
//第二个
cc.log(arguments)
}
}
@ccclass
export default class ClickTest extends cc.Component {
start () {
this.node.on(cc.Node.EventType.MOUSE_UP, this.onMouseUp,this)
}
onMouseUp(){
person.newBind(tmp,'1a','2a')('3c');
}
}
结果:
第一个arguments里面包含第一个括号里面的参数
第二个arguments里面包含第二个括号里面的参数
那就只需要进行切割然后用concat合并就好了:
var tmp = {
name:'我的名字',
};
function person(a,b,c){
cc.log(this.name);
cc.log(a,b,c);
}
Function.prototype.newBind = function(obj){
var that = this,
//获取第一个括号内的参数
arr = Array.prototype.slice.call(arguments,1);
return function(){
//arr2获取第二个括号里面的
var arr2 = Array.prototype.slice.call(arguments),
// arrSum合并两个括号里面的参数(除了第一个参数外)
arrSum = arr.concat(arr2);
that.apply(obj,arrSum);
}
}
@ccclass
export default class ClickTest extends cc.Component {
start () {
this.node.on(cc.Node.EventType.MOUSE_UP, this.onMouseUp,this)
}
onMouseUp(){
person.newBind(tmp,'1a','2a')('3c');
}
}
结果: