https://www.jb51.net/article/141141.htm
var a={ val: 1 } let _value = a.val Object.defineProperty(a,"val",{ get:function(){ return _value }, set:function(newVal){ _value = newVal console.log("setted") } }) a.val;//1 a.val=[];//setted a.val=[1,2,3];//setted a.val[1]=10;//无输出 a.val.push(4);//无输出 a.val.length=5;//无输出 a.val;//[1,10,3,4,undefined];
可以看到,当a.b被设置为数组后,只要不是重新赋值一个新的数组对象,任何对数组内部的修改都不会触发setter方法的执行。这一点非常重要,因为基于Object.defineProperty()方法的现代前端框架实现的数据双向绑定也同样无法识别这样的数组变化。因此第一点,如果想要触发数据双向绑定,我们不要使用arr[1]=newValue;这样的语句来实现;第二点,框架也提供了许多方法来实现数组的双向绑定。
Array.prototype.push.apply()
var a = [1,2,3]; var b = [4,5,6]; a.push.apply(a, b); console.log(a) //[1,2,3,4,5,6]
等同于
var a = [1,2,3]; var b = [4,5,6]; Array.prototype.push.apply(a, b); console.log(a) //[1,2,3,4,5,6]
原生push方法接受的参数是一个参数列表,它不会自动把数组扩展成参数列表,使用apply的写法可以将数组型参数扩展成参数列表,这样合并两个数组就可以直接传数组参数了。
合并数组为什么不直接使用Array.prototype.concat()呢?
因为concat不会改变原数组,concat会返回新数组,而上面apply这种写法直接改变数组a。
Array.prototype.push.call()
var obj = {} console.log(Array.prototype.push.call(obj, 'a','b','c')) // 3 console.log(obj) // {0: "a", 1: "b", 2: "c", length: 3} var obj1 = { length: 5 } console.log(Array.prototype.push.call(obj1, 'a','b','c')) // 8 console.log(obj1) // {5: "a", 6: "b", 7: "c", length: 8} var obj2 = { 0: 'e', 1: 'f', length: 7 } console.log(Array.prototype.push.call(obj2, 'a','b','c')) // 10 console.log(obj2) // {0: "e", 1: "f", 7: "a", 8: "b", 9: "c", length: 10}
通过上面对比结果,我们可以看出:
1)当对象中不含有length属性时,调用数组原型方法push,将对象转为类数组对象后,length则代表的是新增的属性个数
2)当对象中含有length属性时,新增属性的索引命名是根据length来的。
eg: obj1中length为5,新增加属性的索引分别为5、6、7;obj2中length为7,新增加属性的索引分别为7、8、9
Array.prototype.slice.call()
Array.prototype.slice.call()方法是只能在类数组上起作用的,并不能同push()方法一样可以可以使对象转换为带有length属性的类数组对象。
a对象都还是{0:”banana”,1:”apple”},但调用数组push()方法后,现在的a:{0:”twobanana”,1:”apple”,length:1}。结合之前我们得出的结论,当对象中没有length属性时,默认添加的新属性索引应为0,因为a中已经有为0的key了,于是将原来的banana覆盖了,便有了现在的结果。
类数组的概念:
(1)拥有length属性,其它属性(索引)为非负整数(对象中的索引会被当做字符串来处理,这里你可以当做是个非负整数串来理解);
(2)不具有数组所具有的方法,,不能直接使用上面的方法,但是可以间接通过call方法改变this指针(即宿主对象),从而使用。
ar arrayPush = {}; (function(method){ var original = Array.prototype[method]; arrayPush[method] = function() { // this 指向可通过下面的测试看出 console.log(this); return original.apply(this, arguments) }; })('push'); var testPush = []; testPush.__proto__ = arrayPush; // 通过输出,可以看出上面所述 this 指向的是 testPush // [] testPush.push(1); // [1] testPush.push(2);
在官方文档,所需监视的只有 push()、pop()、shift()、unshift()、splice()、sort()、reverse() 7 种方法。我们可以遍历一下:
var arrayProto = Array.prototype var arrayMethods = Object.create(arrayProto) ;[ 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse' ].forEach(function(item){ Object.defineProperty(arrayMethods,item,{ value:function mutator(){ //缓存原生方法,之后调用 console.log('array被访问'); var original = arrayProto[item] var args = Array.from(arguments) original.apply(this,args) // console.log(this); }, }) })