自己动手实现ArrayList

我们用的最多的就是ArrayList类了。
Java提供ArrayList类作为数据结构中数组的实现,其内部无非是封装了原始的数组。
先不看jdk里的源代码,我们自己试着实现一下吧。
pubic class MyArrayList
首先我们需要一个原始的数组作为成员变量Object[];
然后我们开始定义需要提供的操作。无外乎是增删改查,其中增删改都是传入指定位置下标进行操作,我们也可以额外提供不指定位置的增加操作,默认在尾部增加。
当然我们还要对各种非法操作进行判断并抛出异常,比如数组大小为5,访问第10个位置的操作就为非法操作。
最后我们可以试着实现Iterable接口

public class MyArrayList {

    private Object[] elements;

    /**
     * 默认的构造函数,初始化内部数组,默认大小为0
     */
    public MyArrayList() {
        elements = new Object[0];
    }

    /**
     * 根据下标获取元素
     */
    public E get(int index) {
        return (E) elements[index];
    }

    /**
     * 根据下标设置元素的值
     */
    public void set(int index, E element) {
        elements[index] = element;
    }

    /**
     * 根据下标移除元素,数组需要变小
     */
    public E remove(int index) {
        E element = get(index);
        Object[] oldElements = elements;
        elements = new Object[oldElements.length - 1];
        for (int i = 0; i < index; i++) {
            elements[i] = oldElements[i];
        }
        // 下标之后的所有元素向前移位
        for (int i = index; i < elements.length; i++) {
            elements[i] = oldElements[i + 1];
        }
        return element;
    }

    /**
     * 在队尾插入元素,数组需要扩容
     */
    public void add(E element) {
        Object[] oldElements = elements;
        // 建立新的更大的数组
        elements = new Object[oldElements.length + 1];
        // 拷贝
        for (int i = 0; i < oldElements.length; i++) {
            elements[i] = oldElements[i];
        }
        elements[oldElements.length] = element;
    }

    public void add(int index, E element) {
        Object[] oldElements = elements;
        elements = new Object[oldElements.length + 1];
        for (int i = 0; i < index; i++) {
            elements[i] = oldElements[i];
        }
        elements[index] = element;
        for (int i = index + 1; i <= oldElements.length; i++) {
            elements[i] = oldElements[i - 1];
        }
    }
}

上面的代码写的太粗糙,可以称得上是一个最差实践。这些代码只提供一个思路,没有任何越界判断和校检数据,也没有垃圾回收意识,实际工程中千万不要这样写。

写上面这些代码主要是深化思想上的一些东西,我们的ArrayList目的是封装对一个数组的操作,所有提供的方法间接的操纵数组,当然一些方法需要生成新的数组,将原有数据和新数据复制进去,从而达到对数组扩容的目的,而使用者并不需要关心内部变化。

从设计到编写这些代码,充分体现了Java这门语言的封装特性。我们也明白那些复杂和庞大的东西细化之后内部总是最简单的。

由于我代码写的很不严谨和完善,之后实现Iterable接口的工作已经没有动力做下去了。可以看看我的这篇博客。
Iterable和Iterator

协变数组covariant array

协变数组covariant array

来看两行Java代码

Object[] list = new Integer[10];
list[0] = "A"; 

这两行代码编译不会报错,运行会抛出一个错误java.lang.ArrayStoreException
我们从头来了解这里面的奥妙。
许多语言支持子类,Java也不例外。
举个例子,Cat是Animal的子类,那么Animal声明可以引用Cat对象,比如表达式中用Cat对象Animal,返回值中用Cat替换Animal。

public Animal get(){
    Animal a = new Cat();
    Cat b = new Cat();
    return b;
}

上面这些代码我们都不陌生,这是子类对象和父类对象之间is-a的关系,Cat is an Animal。
但是Cat[]与Animal[]之间的关系是怎样的呢。于是编程语言的设计者要考虑和决定由此衍生的问题,比如数组、继承、泛型等等。
一般出现4种定义

协变 covariant a Cat[] is an Animal[]
逆变 contravariant an Animal[] is a Cat[]
双变 bivariant an Animal[] is a Cat[] and a Cat[] is an Animal[]
不变 invariant an Animal[] is not a Cat[] and a Cat[] is not an Animal[]

拿”协变 covariant”举个例子,如果Cat和Dog都是Animal子类,Cat[]可以作为Animal[]对待,我们可以把Dog放到Animal[]里,但我们知道不能把Dog放到Cat[]里,所以存放有问题。
再说说”逆变 contravariant “,如果Cat和Dog都是Animal子类,Animal[]可以作为Cat[]对待,我们从Cat[]中期待只能取到Cat,但Animal[]可能存有Dog被我们取到。

由此可见,如果我们为了保证对数组的读和写都不会有类型错误,最安全的做法是选择”不变 invariant”
即规定这样的声明是错误的 Animal[] list = new Cat[];
但是Java中并没有这样做,而是选择了”协变 covariant “。为什么呢。
这是有历史原因,Java 1.2版本中才引入了泛型,而最初为了让ArrayList等一系列类可以正常工作,只能这样选择。
ArrayList内部保存一个Object[],封装了一系列的对数组的操作。Object是所有类型的父类,如果不是协变的,我们无法存取它的子类,比如我们创建一个ArrayList却不能存放String类型,这将是多大的不便。
当然为了正确处理异常,定义了java.lang.ArrayStoreException

不过,协变引起的问题不仅仅是这一点。
如果你看过ArrayList的源码,会在其中一个构造方法中看到这样一行注释

public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        size = elementData.length;
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
}

仔细想想就能明白其中原因。我就不多说了。
在官网上根据编号查看这个bug的详细信息时,有个很有意思的回复,[bug是2005-04-25提交的]
http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6260652

10 years later, I still believe this is “just” a bug that should be fixed.

2015-06-26

参考https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)

java.lang.Iterable 和 java.util.Iterator 和 java.util.Enumeration

这三个接口都为泛型接口,定义简单
先来看一看Iterable接口,@since 1.5。
而Iterable存在的目的就是使实现它的类可以使用foreach循环

public interface Iterable {
    Iterator iterator();
}

里面只有一个方法,就是返回一个Iterator,用于迭代。
我们马上来看一看Iterator接口

public interface Iterator {
    boolean hasNext();  
    E next();
    void remove();
}

也很简单,三个方法,一个判断是否有后续元素,一个获取后续元素,一个删除当前元素。
这里要注意remove方法,因为是删除当前元素,所以调用remove方法前,至少要调用一次next方法。
一般实现时通过一个标识变量,在next方法中修改为可remove状态,在remove方法中先判断标识,然后remove,最后变量改为不可remove状态。
我们来写一些非常简单的代码试一试。

public class MyList implements Iterable {

    // 定义一个数组
    private String[] list = { "A", "B", "C" };

    // 实现Iterable接口
    public Iterator iterator() {
        return new MyIterator();
    }

    // 实现Iterator接口
    public class MyIterator implements Iterator {

        private int i = 0;

        public boolean hasNext() {
            return i < 3;
        }

        public String next() {
            return list[i++];
        }

        public void remove() {
            // TODO 我比较懒,这里和我要测的foreach关系不大,就不写了。
        }
    }

    public static void main(String args[]) {
        MyList list = new MyList();
        for (String var : list) {
            System.out.println(var);
        }
    }
}

输出结果
A
B
C

Iterable定义在1.5版本,Itertor定义在1.2版本。
而定义在最初的1.0版本用于枚举和迭代的,是Enumeration接口。
Itertor是作为Enumeration的替代,为了向下兼容,Enumeration并没有标注弃用

public interface Enumeration {
    boolean hasMoreElements();
    E nextElement();
}

Itertor相比Enumeration有两点改进
1.增加了remove方法
2.简化了方法名。

《苹果DNA》

一个人也许有全世界最棒的想法——非常新奇、与众不同,但如果他无法说服其他人认同这个想法,一切都是白费。
——摘自《苹果DNA》

逆势之下,真正的王道是创造出伟大的产品。
——摘自《苹果DNA》

大道至简。
——摘自《苹果DNA》

没有平庸的员工,只有平庸的管理者。
——摘自《苹果DNA》

细节成就完美。
——摘自《苹果DNA》

成功的演说:编织故事、设计情节;精悍的标题;灵活运用手势等肢体语言;制造一个满场惊叹的时刻;设计一个英雄和一个反派;用视觉思考;与合作伙伴分享舞台;兜售梦想而非产品。
——摘自《苹果DNA》

兜售梦想,而非产品。
——摘自《苹果DNA》

时间有限,我们不应该为别人而活。活着是为改变世界。
——摘自《苹果DNA》

凡是最美妙的想法都是来自追逐成功的激情。人生短暂,不要为了别人眼中的标准而活。
——摘自《苹果DNA》

组建一个团队并不难,难的是如何把团队成员凝聚在一起,并让他们为了一个共同目标而努力奋斗。即使又在与众不同的做法,再创意十足的团队,对于领导者而言,最核心的还是你是否有清晰而远大的目标。
——摘自《苹果DNA》

《你不知道的JavaScript(上卷)》(Scope & Closures & This & Object Prototypes)

第一部分Scope
第一章 作用域是什么
作用域是一套规则,用于确定在何处以及如何查找变量(标识符)。如果查找的目的是对变量进行赋值,那么就会使用LHS查询;如果目的是获取变量的值,就会使用RHS查询。赋值操作符会导致LHS查询。“=”操作符或调用函数是传入参数的操作都会导致关联作用域的赋值操作。
不成功的RHS引用会导致抛出ReferenceError异常。不成功的LHS引用会导致自动隐式地创建一个全局变量(非严格模式),该变量使用LHS引用的目标作为标识符,或者抛出ReferenceError异常(严格模式)。
第二章 词法作用域
词法作用域意味着作用域是由书写代码时函数声明的位置来决定的。编译的词法分析阶段基本能够知道全部标识符在哪里以及是如何声明的,从而能够预测在执行过程中如何对他们进行查找。
JavaScript中有两个机制可以“欺骗”词法作用域:eval和with。前者可以对一段包含一段或多段声明的“代码”字符串进行演算,并借此在运行时修改已经存在的词法作用域。后者本质上通过将一个对象的引用当做作用域来处理,将对象的属性当做作用域的标识符来处理,从而在运行时创建一个新的词法作用域。
这两个机制的副作用是引擎无法在编译时对作用域查找进行优化。
第三章 函数作用域和块作用域
IIFE(Immediately Invoked Function Expression)
函数是JavaScript中最常见的作用域单元。本质上,声明在一个函数内部的变量或函数会在所处的作用域中“隐藏”起来,这是有意为之的良好软件的设计原则。
第四章 提升
我们习惯将var a=2;看做一个声明,而实际上JavaScript引擎并不这样认为,它将var a和a=2当做两个单独的声明,第一个是编译阶段的任务,第二个是执行阶段的任务。
第五章 作用域闭包
一个非常重要但又难以掌握,近乎神话的概念:闭包。
对于那些有一点JavaScript使用经验但从未真正理解闭包概念的人来说,理解闭包可以看做是某种意义上的重生。
闭包:当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行。
闭包就好像从JavaScript中分离出来的一个充满神秘色彩的未开化的世界,只有最勇敢的人才能够到达那里。但实际上它只是一个标准,显然就是关于如何在函数作为值按需传递的词法环境中书写代码的。
附录
try/catch实现块作用域
{try{throw undefined;}catch(a){console.log(a);}}
IIFE和try/catch并不是完全等价的,因为如果将一段代码的任意部分拿出来用函数进行包裹,会改变这段代码的含义,其中的this、return、break和continue都会发生变化。IIFE并不是一个普适的解决方案。

第二部分this
第一章 关于this
任何足够先进的技术都和魔法无异。————Arthur C.Clarke
在一个函数内部可以通过自己的函数名引用自身。argument.callee是一种传统的但是现在已经被弃用和批判的用法。
this不是指向函数本身,this不是指向函数的作用域。this是在运行时绑定的,并不是在编写时绑定的。this的上下文取决于函数调用时的各种条件,只取决于函数的调用方式。
第二章 this全面解析
影响this绑定的情况
不带任何修饰函数调用的默认绑定,调用者的隐式绑定,apply和call的显示绑定,bind的硬绑定,new的绑定。
上述5种绑定方法,绑定强度由弱到强。
判断一个运行中函数的this绑定,就需要找到这个函数的直接调用位置。然后按照顺序应用以下4条规则。
由new调用,绑定到新创建的对象。
由call,apply,bind调用,绑定到指定对象。
由上下文对象调用,绑定到那个上下文对象。
默认:严格模式下绑定到undefined,否则绑定到全局对象。
第三章 对象
JavaScript中的对象有字面形式var a = {};和构造形式var a = new Object();
属性描述符
value属性的值
writable属性的值是否可写
enumerable属性是否可枚举(出现在for-in循环)
configurable属性是否可配置(修改描述符)
Object.preventExtensions(obj)禁止添加新属性
Object.seal(obj)禁止添加新属性,也不能配置现有属性。= preventExtensions()+configurable:false
Object.freeze(obj)禁止任意修改 = seal() + writable:false
当你给一个属性定义getter或setter是,这个属性变成访问描述符。对于访问描述符,JavaScript会忽略他们的value和writable特性,取而代之的是关心set和get特性。
第四章 混合对象”类”
类是一种设计模式。许多语言提供了对于面向类软件设计的原生语法。JavaScript也有类似的语法,但是和其他语言中的类完全不同。
类意味着复制。
传统的类被实例化时,它的行为会被复制到实例中。类被继承时,行为也会复制到子类中。
多态(在继承链的不同层次名称相同但功能不同的函数)看起来似乎是从子类引用父类,但本质上引用的其实是复制的结果。
JavaScript并不会像类那样自动创建对象的副本。
第五章 原型
当你试图引用对象的属性时会触发[[Get]]操作,然后沿着原型链搜索。
Object.create = function(o){
function F(){};F.prototype = o; return new F();
}
使用new调用函数时会把新对象的.prototype属性关联到“其他对象”。带new的函数调用通常被成为“构造函数调用”尽管他们实际上和传统面相类语言中的类构造函数不一样。虽然这些JavaScript机制和传统面相类语言中的“类初始化”和“类继承”很相似,但是JavaScript中的机智有一个核心区别,那就是不会进行复制,对象之间是通过内部的[[prototype]]链关联的。
第六章 行为委托
在软件架构中你可以选择是否使用类和继承设计模式。大多数开发者理所当然的认为类是唯一的代码组织方式,但在JavaScript中更强大的设计模式,行为委托。
行为委托认为对象之间是兄弟关系,互相委托,而不是父类和子类的关系。JavaScript的[[prototype]]机制本质上就是行为委托机制。
对象关联是一种编码风格,它倡导的是直接创建和关联对象,不把它们抽象成类。对象关联可以用基于[[prototype]]的行为委托非常自然地实现。

《极客》

这本书的作者很扭曲,几乎每隔一页都会引用一句不知名的晦涩的中国古典诗句、或国外诗歌,让读者在硅谷的新时代科技浪潮巅峰突然感受到悠久渊源的文化,这是一种什么感觉啊。“毫无违和感”。

看硅谷的商业纷争,就像七雄争霸,各个公司纵横捭阖,驰骋商场,商场如战场,没有硝烟的战争,一个又一个公司崛起,辉煌,落寞,消失,只有少数的胜者,站立到最后,也笑到最后。

微软恰似如今腾讯,不断在其他相关领域模仿先驱者,然后占据市场,排挤掉一个又一个对手。为什么他们能够成功,不会因业务不熟练而垮掉。我思考了一下,想明白了,他们是基础,微软操作系统是应用程序的基础,腾讯QQ是社交通讯的基础,这种基础能够聚拢大量的用户,让用户离不开这种基础,然后用户就可以任由他们摆布了。

微软有天下无敌的三绝招,那就是:打不过你就模仿你;在打不过就和你比流血,看谁流的久;最后如果还不行的话,那就挖光你的人。

市场终将打败技术。

施乐造梦厂。

只有一种成功——能以你自己的生活方式度过你的一生。——《极客》

一个人生命中最大的幸运,莫过于在他人生途中,即年富力强时发现自己生活的使命。——斯蒂芬·茨威格

《大设计》

我们看到的直线运动,能够做出方程,并预言其运动;金鱼在圆形鱼缸里也能做出曲线方程,并预言其运动。二者都是正确的。

蚂蚁在曲面上爬行,能根据路程确定自己不是在二维空间,那我们也能通过测量确定所处的四维空间。

所谓波粒二象性,只不过是我们无法想象出的一个模型。

我们习惯于三维空间,是因为其他维度太小了,就像在远处看一根吸管,看似是一条线,实际是它直径太小了。

我们人类只不过是自然的基本粒子的聚集。

任何的复杂,都是有意志和思想的。

“生命游戏”“费恩曼图”

《大连接:社会网络是如何形成的以及对人类现实行为的影响》(Connected:The Surprising Power of Our Social Networks and How They Shape Our Lives)

三度影响力

电路网络,神经网络,基因网络,五花八门的网络,但我们的网络更有意思:更复杂也更重要。一个由人组成的网络有着不同寻常的生命。

我们的相互连接关系不仅是生命中与生俱来、必不可少的一个组成部分,更是一种永恒的力量。正像大脑能够做单个神经元所不能做的事情一样,社会网络能够做的事情,仅靠一个人是无法完成的。

我们可以创建连接关系网络示意图。我们可以从社交网站的大量数据中导出表现联系的数据,然后建立三维示意图,顶点代表个体,连线代表联系。具体呢。连线长短表现联系紧密程度,顶点颜色表现性别或是活跃度或是其他,处于中心位置的顶点肯定是连线多的。我们可以从这种示意图中获得太多太多的有关社会网络的信息,比如显示一则被转发信息的流动,划分不同群体范围,用于研究网络传播、人际关系、各种理论。但是怎么生成呢。通过数据库,矩阵?搜索引擎的算法?三维图表现的完全吗,会不会太杂乱了,为什么要用三维图呢,二维图肯定是表现不出来这种复杂的联系的,那么四维图、十二维图呢,应该基于社会网络的连接属性吧,那么真正社会网络的连接是几维的呢,用三维图是因为我们能表现出来的最复杂的只有三维吧。大数据怎么处理呢,如果一个人关注一百多个人,用一百多条线吗

蛋糕的味道,是面粉与鸡蛋都没有的,也不是介于面粉与鸡蛋的味道。它的味道远不止这些。蛋糕的味道,超出了所有原料味道的简单相加。

三度影响力原则Three Degrees of Influence Rule
我们所做或所说的任何事情,都会在网络上泛起涟漪,影响我们的朋友(一度),我们朋友的朋友(二度),甚至我们朋友的朋友的朋友(三度)。如果超出三度分隔,我们的影响就会逐渐消失。相距三度之内的人之间是强连接关系,强连接可以引发行为。

“我们镶嵌在社会网络上”,我们之所以希望形成连接关系,最根本的原因是我们的基因在起作用。连接行为本身就是自然选择的结果。我们必须与他人合作,判断他们的意图,影响他们或者被他们影响。自利并不总是有利可图的,与那些只关心自己的人相比,乐意帮助他人的人,生存下来的可能性更大。
想象一下原始人,只有懂得合作才能生存下来,通过自然选择,淘汰了那些自利的人。于是,现在我们进化到将这一理念融入基因,与生俱来的合作的思想。

《JavaScript专家编程》(Expert JavaScript)

第一章 对象和原型
new运算符是一种JavaScript试图让自己类似于Java的退化结构。很多人都会对new运算符感到很疑惑,因为它强加了一种伪类动词到JavaScript中,但JavaScript并不是一种正规的基于类继承方法的语言。
ECMAScript 5 引入的Object.create方法。
Object.create()和字面量对象应该替换new Object()这种方法。
面向对象
抽象:把真实世界的对象或过程转化为可计算的模型。
封装:隐藏实现、提升模块化以及保护对象内部的状态。
多态:在特定上下文,对象表现不同。
继承:定义对象间语义化的层级。

当对象检视属性时,它会查询原型链的每一环,直到返回所需的值或者undefined。
当一个原型链的对象设置一个原型链上已经存在的属性时,原型的属性不会改变;取而代之的是,属性会被定义到但前对象上,不能访问远端的原型链。
第二章 函数
函数有两种形式:函数声明和函数表达式。语法解析时,JavaScript会把所有函数声明移到当前作用域的顶端,忽略赋值表达式。
这就是function foo(){}和var bar = function foo(){}产生不同表现的原因。第二个foo()是赋值变量的一部分,拥有自己的作用域。
在JavaScript中,for循环中的x可以被访问到,控制流语句是封闭作用域的一部分。对很多习惯了块级作用域的开发者来说这很不符合直觉。
IIFE有很多方式来写
;!fuction(){}()
运用一个一元表达式,分号是为了防御式编程,防止其他模块没有以分号结尾。
JavaScript是函数级作用域,这跟许多采用块级作用域的语言有所不同。
函数的参数对象仅仅像一个数组。
第三章 闭包
闭包就是将所有自由变量和函数绑定在一个封闭的表达式中,这个表达式可以保留在自由变量和函数创建之外的词法作用域。
闭包的最基本形式就是在一个函数内部返回另一个函数,这样就产生了一个在需要的时候返回封闭作用域的机制。
第四章 术语和俚语
编程术语是通过使用语言的高度特异性的技术规则而形成的代码压缩。像其他形式的术语一样,这种编程的形式用于在社区的成员之间高效地表达复杂的想法。
强转:
转换为String。首先调用toString()。如果不返回基础类型的值,调用valueOf()。如果也不反悔基础类型的值,抛出TypeError异常。
转换为数字。一元运算符的职责是将运算符后的操作数转换为数字。先调用valueOf(), 后调用toString()
第五章 异步生活
JavaScript的单线程意味着每个运行时进程只有一个事件循环。JavaScript的事件循环主要受到两个概念的影响,运行至完成(run-to-completion)和非阻塞输入/输出(I/O).
堆(Heap)。堆是内存中顺序无关的容器。堆是JavaScript存放正在使用或者未被垃圾回收清理的变量和对象的地方。
桢(Frame)。桢是事件循环周期中需要被执行的连续工作单元。桢包含把函数对象和堆中的变量连接在一起的执行上下文。
栈(Stack)。事件循环栈包含了执行一个消息所需的所有连续的桢。事件循环自顶向下处理桢。桢根据彼此的依赖关系链被添加到栈中。有依赖的桢把它们所依赖的桢添加到上面。这个过程确保了依赖关系在被代码引用前得到满足。
队列(Queue)。队列是等待处理的消息的列表。每个消息都引用一个JavaScript函数。当栈为空时,队列中最旧的消息作为底部桢被添加到栈中。
JavaScript是单线程的语言,这意味着并发往往是由其他方式伪装的。JavaScript的事件循环被设计成非阻塞的I/O操作。JavaScript中的回调是把一个函数对象作为参数传递给另一个函数,并在返回值中使用的行为。
第六章 JavaScript的IRL
第七章 风格
第八章 工作流
第九章 代码质量
第十章 提高可测试性

《JavaScript面向对象精要》(The principles of object-oriented javascript)

第一章 原始类型和引用类型
JavaScript中没有类,有类型。每个变量或者数据都有一个对应的原始类型或者引用类型

5种原始类型保存在变量对象中:string,number,boolean,null,undefined
除了null都可以用typeof来鉴别。typeof null =Object,null必须和null进行比较才能鉴别。
引用类型是JavaScript中最接近类的东西,对象就引用类型的实例。可以用new操作符或字面形式创建新对象。函数也是对象,可以用typeof鉴别。
在引用类型中,JavaScript提供内建类型:Array、Date、Error、Function、Object、RegExp
3种封装类型:String、Number、Boolean
当你对原始类型进行调用封装类型的方法时,JavaScript创建封装类型的临时实例,在临时实例上调用方法,但会在语句结束后销毁实例。

第二章 函数
函数是对象。函数有独有一个[[call]]内部属性,这也是typeof判断为function的依据。
函数声明与函数表达式唯一的区别,声明会被JavaScript提升至上下文。
函数是对象,所以它能当做参数传递。
可以给函数传递任意数量的参数,全部保存在arguments属性中,类似数组,并不是数组的实例。函数的length属性表示定义时期望的参数个数。
根据传入的参数调用相同函数名的不同方法,叫做重载。JavaScript只能通过手动判断arguments对象模仿重载.
JavaScript所有的函数作用域内都有一个this对象代表调用该函数的对象。
函数是对象,对象有方法,函数也有,函数有3种方法改变this
call(指定的this值,参数)执行
apply(指定的this值,[参数])执行
bind(指定的this值,永久绑定的参数)创建新函数

第三章 理解对象
2种创建对象的方式:{}和new Object()
对象的属性可以增加修改删除,属性和值就像哈希表的键值对。
for-in会遍历所以设置为可枚举的属性,包括原形属性。Object.keys()返回自由属性的数组。
属性有2种类型:数据属性和访问器属性。
两种类型都有[[Enumerable]]和[[Configurable]]特征
数据属性还具有[[Writable]]和[[Value]]特征
访问器属性则具有[[Get]]和[[Set]]特征
禁止修改对象
1.禁止扩展。无法添加新属性。Object.preventExtensions(对象)
2.封印。无法添加,无法删除,无法改变属性的类型。只能读写。Object.seal(对象)
3.冻结。无法添加,无法删除,无法改变属性的类型,无法写入。只读,相当于对象某个时间点上的快照。Object.freeze()

第四章 构造函数和原型对象
构造函数就是用new操作符调用的普通函数。
可以把原型对象看作是对象的基类。所有创建的对象实例共享该原型对象。
一个对象实例通过内部属性[[Prototype]]跟踪其原型对象。原型对象的[[constructor]]属性指向该函数。

第五章 继承
Object.create(被设置为prototype的对象,属性描述)
当你定义一个函数时
function YourConstructor(){}
JavaScript引擎会做以下事情
YourConstructor.prototype=Object.create(Object.prototype,{
constructor:{
configurable:true,
enumerable:true,
value:YourConstructor,
writable:true
}
});
访问父类方法,需要通过call()或apply()调用父类的原型对象的方法时传入子类的对象。

第六章 对象模式
IIFE(立即函数表达)定义后立即调用并产生结果的函数表达,执行后立即被销毁。