这两天自己在写代码的时候,出现一个BUG,代码如下:
class Car {
constructor(carId) {
this.position = [114, 130]
this.path = []
this.speed = Math.floor(Math.random() * 5)
this.timer = null
}
run(){
this.position[0] += this.speed * (Math.random() * 2 == 1 ? 1 : -1)
this.position[1] += this.speed * (Math.random() * 2 == 1 ? 1 : -1)
this.path.push(this.position)
if(this.path.length > 10){
this.path.shift()
}
}
start(){
this.timer = setInterval(function(){
this.run()
}, 1000)
}
stop(){
clearInterval(this.timer)
}
}
var car = new Car("10086")
car.start()
代码预期的结果是,记录car的最近10个坐标点。
但是实际结果大失所望,得出的是10个一模一样的坐标点,原因在于调用run方法时,其中坐标的改变是基于其属性position这个数组对象的改变,而数组对象的变量名其实是对数组对象地址的引用,因此导致了最后一个坐标的改变引起了所有坐标的改变。
通过这个BUG对自己的基础知识又进行了一次梳理,归纳以及总结,参考资料为JavaScript高级程序设计:
知识梳理
javascript变量的数据类型:
1:基础类型 : Undefined、null、Boolean、Number和String
2:引用类型 : object
其中引用类型的赋值操作需要注意,因为引用类型的值是按引用访问的,且具有动态属性,会根据取得其引用的变量的操作而改变该引用的内存对象发生改变。取复制变量的例子用图示的方法来解释:
如下代码:
var num1 = 5
var num2 = num1
基本类型的赋值就相当于创建一个num1的副本,同时将num2的值等于该副本,两个变量之间的操作互不影响。
图示如下:
而对于引用类型的复制可不是这样
var num1 = obj1
var num2 = num1
这个复制只是将num1的引用赋值给num2,二者是属于同一个引用,访问的都是堆内存中的同一个对象,任何一个该引用的变量发生变化,会对其余使用该引用的变量也发生变化。
函数参数的传参
在JS中函数参数的传参方式都是按值传参的
可以近似看成函数内部声明一个局部变量名为参数名字的变量,同时为其赋值为参数的值,参数为引用类型则较为复杂些,主要是按值传递比较难理解。
传递的参数为引用类型的话,即函数内部该参数发生了改变会引起堆内存对象的属性发生改变,那么为什么不叫按引用访问,资料中有如下代码进行解释:
function setName(obj) {
obj.name = "Nicholas";
obj = new Object();
obj.name = "Greg";
}
var person = new Object();
setName(person);
alert(person.name); //"Nicholas"
书中的解释比较简洁,个人理解为如果是按引用传参则会发生堆内存的对象会发生改变,由原本的实例person将被新的new Object的实例,同时将其属性name置为"Greg",即最终obj指向的是new Object的实例,而事实上没有,可以理解为函数的引用类型的参数为引用类型的引用,且这里对引用的处理方式是类似基本类型的值一般,不会发生变化。
图示如下:
归纳总结
基础知识梳理完毕,回到我的BUG,犯的错误就是引用类型的访问方式的错误,path所push的position数组准确来说指向的都是同一个对象,因此position的每次变化,数组中所有的元素都会发生相同的变化,导致path数组的元素均为一致.
为此对数组的方法进行一次归纳,将数组中可以返回新数组副本(即对原数组无影响)的方法,以方便避免像我这种使用导致的BUG
返回新数组副本方法:concat, slice, splice, filter, map
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。