Js再起之对象和迭代
js的对象有一大堆细节, 然并卵. 只要记着js是基于prototype的就好了.
遍历
for...in 循环
该方法依次访问一个对象及其原型链中所有可枚举的属性。
Object.keys(o)
该方法返回一个对象 o 自身包含(不包括原型中)的所有属性的名称的数组。
Object.getOwnPropertyNames(o)
该方法返回一个数组,它包含了对象 o 所有拥有的属性(无论是否可枚举)的名称。
也可以这么做(古代方法):
function listAllProperties(o){
var ob;
var re = [];
for(ob = o; ob !== null; ob = Object.getPrototypeOf(ob)){
re = re.concat(Object.getOwnPropertyNames(ob));
}
return re;
}
集合/对象
var obj = {
name: "Carrot",
"for": "Max",
details: {
color: "orange",
size: 12
}
}
obj.details.color; // orange
obj["details"]["size"]; // 12
//数组
var a = ["dog", "cat", "hen"];
a.length; // 3 就这个特殊了. 否则数组就是个集合.
//遍历有四个方法
for (var i = 0; i < a.length; i++)
for (var i in a)
a.forEach(function()
//有用的方法
push
pop
shift
//函数
function add() {
var sum = 0;
for (var i = 0, j = arguments.length; i < j; i++) {
sum += arguments[i];
}
return sum;
}
add(2, 3, 4, 5); // 14
add.apply(null, [2, 3, 4, 5]); // 传入一个数组
//可以匿名
var a = 1;
var b = 2;
(function() {
var b = 3;
a += b;
})();
a; // 4
b; // 2
//递归调用, 遍历节点树.
var charsInBody = (function counter(elm) {
if (elm.nodeType == 3) { // 文本节点
return elm.nodeValue.length;
}
var count = 0;
for (var i = 0, child; child = elm.childNodes[i]; i++) {
count += counter(child);
}
return count;
})(document.body);
//非prototype方式的集合
function makePerson(first, last) {
return {
first: first,
last: last,
fullName: function() {
return this.first + ' ' + this.last;
},
fullNameReversed: function() {
return this.last + ', ' + this.first;
}
}
}
s = makePerson("Simon", "Willison");
s.fullName(); // Simon Willison
s.fullNameReversed(); // Willison, Simon
//function放到prototype总是更好的方法
function Person(first, last) {
this.first = first;
this.last = last;
}
Person.prototype.fullName = function() {
return this.first + ' ' + this.last;
}
Person.prototype.fullNameReversed = function() {
return this.last + ', ' + this.first;
}
//prototype可以后找补.
s = new Person("Simon", "Willison");
s.firstNameCaps(); // TypeError on line 1: s.firstNameCaps is not a function
Person.prototype.firstNameCaps = function() {
return this.first.toUpperCase()
}
s.firstNameCaps(); // SIMON
//apply的说明
function trivialNew(constructor, ...args) {
var o = {}; // 创建一个对象
constructor.apply(o, args);//apply的第一个参数就是this的作用域
return o;
}
var bill = trivialNew(Person, "William", "Orange");
var bill = new Person("William", "Orange");
//上面这两句是等价的.
lastNameCaps.call(bill); //假设lastNameCaps这个函数存在.
// 和以下方式等价
bill.lastNameCaps = lastNameCaps;
bill.lastNameCaps();
//闭包
function makeAdder(a) {
return function(b) {
return a + b;
}
}
var x = makeAdder(5);
var y = makeAdder(20);
x(6); // 11
y(7); // 27
//内存泄漏
function addHandler() {
var el = document.getElementById('el');
el.onclick = function() {
el.style.backgroundColor = 'red';//这个el很难被释放.
}
}
function addHandler(){
document.getElementById('el').onclick = function(){
this.style.backgroundColor = 'red';//这样就解决了内存泄漏.
};
}
//避免使用过多的闭包,可以用let关键词
function MyObject(name, message) {
this.name = name.toString();
this.message = message.toString();
}
MyObject.prototype.getName = function() {
return this.name;
};
MyObject.prototype.getMessage = function() {
return this.message;
};
//下面是简洁写法, 但是括号增加了, 其实也难说就好了.
function MyObject(name, message) {
this.name = name.toString();
this.message = message.toString();
}
(function() {
this.getName = function() {
return this.name;
};
this.getMessage = function() {
return this.message;
};
}).call(MyObject.prototype)
几乎照抄: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/A_re-introduction_to_JavaScript
迭代器
//这个我写过......
function* idMaker() { //*星号的意思是, 自动帮你维护迭代状态.
var index = 0;
while(true)
yield index++;// yield是关键, 当这个迭代器的 next() 方法被首次(后续)调用时,其内的语句会执行到第一个(后续)出现yield的位置为止,yield 后紧跟迭代器要返回的值。或者如果用的是 yield*(多了个星号),则表示将执行权移交给另一个生成器函数(当前生成器暂停执行)。
}
var gen = idMaker();
console.log(gen.next().value); // 0
console.log(gen.next().value); // 1
console.log(gen.next().value); // 2
// 不用星*的写法:
function makeIterator(array){
var nextIndex = 0;
return {
next: function(){
return nextIndex < array.length ?
{value: array[nextIndex++], done: false} :
{done: true};
}
};
}
var it = makeIterator(['yo', 'ya']);
console.log(it.next().value); // 'yo'
console.log(it.next().value); // 'ya'
console.log(it.next().done); // true
迭代器的实现
为了实现可迭代,一个对象必须实现 **@@iterator **方法,这意味着这个对象(或其原型链中的一个对象)必须具有带 Symbol.iterator
键的属性:
var myIterable = {};
myIterable[Symbol.iterator] = function* () {
yield 1;
yield 2;
yield 3;
};
for (let value of myIterable) {
console.log(value);
}
// 1
// 2
// 3
or
[...myIterable]; // [1, 2, 3]
参考: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Iterators_and_Generators
元编程
proxy和reflect
let handler = {
get: function(target, name){return name in target ? target[name] : 42}
}
let p = new Proxy({}, handler);//proxy是一个空对象, get属性用handle
p.a = 1;
console.log(p.a, p.b); // 1, 42
参考: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Meta_programming
内存
//循环引用会导致引用计数失灵.
function f(){
var o = {};
var o2 = {};
o.a = o2; // o 引用 o2
o2.a = o; // o2 引用 o
return "azerty";
}
f();
//实际例子:
var div;
window.onload = function(){
div = document.getElementById("myDivElement");
div.circularReference = div;//这里自己引用了自己
div.lotsOfData = new Array(10000).join("*");
};
//但是现代浏览器是根引用判定, 从根部开始遍历, 如果有某个对象在整个引用树中都没有引用, 则这个对象可以回收.
矢量运算simd
SIMD.%type%.select()
SIMD.%type%.bool()
缓冲/写屏(视图)
ArrayBuffer 是一种数据类型,用来表示一个通用的、固定长度的二进制数据缓冲区。你不能直接操纵一个ArrayBuffer中的内容;你需要创建一个类型化数组的视图或一个描述缓冲数据格式的DataView,使用它们来读写缓冲区中的内容
原型链
在原型链上查找属性比较耗时,对性能有副作用
当谈到继承时,JavaScript 只有一种结构:对象。每个对象都有一个私有属性(称之为 [[Prototype]]),它持有一个连接到另一个称为其 prototype 对象(原型对象)的链接。该 prototype 对象又具有一个自己的原型,层层向上直到一个对象的原型为 null。
(译者注:Object.getPrototypeOf(Object.prototype) === null; // true)
根据定义,null 没有原型,并作为这个原型链中的最后一个环节。
JavaScript 中几乎所有的对象都是位于原型链顶端的Object的实例。
var o = new Foo();
//JavaScript 实际上执行的是:
var o = new Object();
o.__proto__ = Foo.prototype;
Foo.call(o);
//然后当你真的做某个调用的时候, js就会在原型链上查找.
//(可以通过Object.getPrototypeOf(obj)或者已被弃用的__proto__属性获得)
//没有官方的方法用于直接访问一个对象的原型对象——原型链中的“连接”被定义在一个内部属性中,在 JavaScript 语言标准中用 [[prototype]] 表示(参见 ECMAScript)。然而,大多数现代浏览器还是提供了一个名为 __proto__ (前后各有2个下划线)的属性,其包含了对象的原型。你可以尝试输入 person1.__proto__ 和 person1.__proto__.__proto__,看看代码中的原型链是什么样的!
1. 那些定义在构造器函数中的、用于给予对象实例的。这些都很容易发现 - 在您自己的代码中,它们是构造函数中使用this.x = x类型的行;在内置的浏览器代码中,它们是可用于对象实例的成员(通常通过使用new关键字调用构造函数来创建,例如var myInstance = new myConstructor())。
2. 那些直接在构造函数上定义、仅在构造函数上可用的。这些通常仅在内置的浏览器对象中可用,并通过被直接链接到构造函数而不是实例来识别。 例如Object.keys()。
3. 那些在构造函数原型上定义、由所有实例和对象类继承的。这些包括在构造函数的原型属性上定义的任何成员,如myConstructor.prototype.x()。
json
JSON.parse(myJSON): 以文本字符串形式接受JSON对象作为参数,并返回相应的对象。。
JSON.stringify(myJSON);: 接收一个对象作为参数,返回一个对应的JSON字符串。
//不论是log对象还是alert对象, 用上面这句话就对了.
对象的打印
JSON.stringify(myJSON)// 此方法ok, 也是唯一可用的方法
JSON.stringify(YOUR_OBJECT_HERE, null, 4)//The second argument alters the contents of the string before returning it. The third argument specifies how many spaces to use as white space for readability.
object.toSource() //不行, 只能firefox
value.toString() //明确的说这种方法不行, 他仅仅返回一个"[Object]"
"" + value //明确的说这种方法不行, 他仅仅返回一个"[Object]"
String(value) //明确的说这种方法不行, 他仅仅返回一个"[Object]"
性能问题
- 闭包有
- 原型链有
- settimeout这种异步也有性能损耗
- 循环引用, 至少给内存回收添了一点小麻烦.