## 常见概念 - 构造函数 - 构造函数-扩展 - 原型规则和示例 - 原型链 - instanceof ## 构造函数 任何一个函数都可以被new,new了之后,就成了构造方法。 如下: ```javascript function Foo(name, age) { this.name = name; this.age = age; //retrun this; //默认有这一行。new一个构造函数,返回一个对象 } var fn1 = new Foo('smyhvae', 26); var fn2 = new Foo('vae',30); //new 多个实例对象 ``` 与普通函数相比,构造函数有以下明显特点: - 用new关键字调用。 - 不需要用return显式返回值的,默认会返回this,也就是新的实例对象。 - 建议函数名的首字母大写,与普通函数区分开。 参考链接: - [JavaScript中的普通函数与构造函数](http://www.cnblogs.com/SheilaSun/p/4398881.html) 当new之后,this会先变成一个空对象,然后通过`this.name = name`来赋值。 ### 构造函数的扩展 ![](http://img.smyhvae.com/20180306_1633.png) 上图中发现,数组、对象、函数也有构造函数,它们的构造函数是Array、Object、function。实际开发中,都推荐前面的书写方式。 ## 原型规则 原型规则是学习原型链的基础。原型规则有五条,下面来讲解。 ### 规则1 所有的引用类型(数组、对象、函数),都具有对象特性,都可以**自由扩展属性**。null除外。 举例: ![](http://img.smyhvae.com/20180306_1651.png) ### 规则2 所有的**引用类型**(数组、对象、函数),都有一个`_proto_`属性,属性值是一个**普通的对象**。`_proto_`的含义是隐式原型。 ![](http://img.smyhvae.com/20180306_1656.png) 其实,规则2是规则1的特例,只不过,js语法帮我们自动加了 规则2。 ### 规则三 所有的**函数**(不包括数组、对象),都有一个`prototype`属性,属性值是一个**普通的对象**。`prototype`的含义是**显式原型**。(实例没有这个属性) ![](http://img.smyhvae.com/20180306_1659.png) ### 规则四 所有的**引用类型**(数组、对象、函数),`_proto_`属性指向它的**构造函数**的`prototype`值。 ![](http://img.smyhvae.com/20180306_1701.png) 总结:以上四条,要先理解清楚,然后再来看下面的第五条。 ### 规则五 当试图获取一个对象的某个属性时,如果这个对象本身没有这个属性,那么会去它的`_proto_`中寻找(即它的构造函数的`prototype`)。 `举例代码1`: ```javascript //创建方法 function Foo(name) { this.name = name; } Foo.prototype.alertName = function () {// 既然 Foo.prototype 是普通的对象,那也允许给它添加额外的属性 alertName console.log(this.name); } var fn = new Foo('smyhvae'); fn.printName = function () { console.log(this.name); } //测试 fn.printName(); //输出结果:smyhvae fn.alertName(); //输出结果:smyhvae ``` 上方代码中,虽然 alertName 不是 fn 自身的属性,但是会从它的构造函数的`prototype`里面找。 **扩展:**遍历循环对象自身的属性 我们知道,`for in`循环可以遍历对象。针对上面的那个fn对象,它自身有两个属性:`name`、`printName`,另外从原型中找到了第三个属性`alertName`。现在,如果我们对fn进行遍历,能遍历到两个属性还是三个属性呢? 答案:两个。因为,**高级浏览器中已经在 `for in`循环中屏蔽了来自原型的属性。但是,为了保证代码的健壮性,我们最好自己加上判断**,手动将第三个属性屏蔽掉: ```javascript for (var item in fn) { if (fn.hasOwnProperty(item)) { console.log(item); } } ``` ## 原型链 还是拿上面的``举例代码1``举例,如果此时在最后面加一行代码: ``` fn.toString(); //去 fn._proto_._proto_ 中查找 toString()方法 ``` 上面的代码中,fn直接调用了 toString()方法,这是因为它通过**原型链**,去`_proto_`的`_proto_`里找到了`Object`,而`Object`是由`toString()`方法的。 ### instanceof 格式: ```javascript 对象 instanceof 构造函数 ``` `instanceof`的作用:用于判断**引用类型**属于哪个**构造函数**。 例1:判断一个变量是否为数组: `变量 instanceof Array` 例2: ```javascript function Person(){ } //p--->Person.prototype--->Object.prototype--->null var p = new Person(); //构造函数的**原型**是否在 p 对象的原型链上! console.log(p instanceof Person); ``` 例3: ```javascript fn instanceof Foo ``` 上面这句话,判断逻辑是:**fn 的`_proto_`一层一层往上找,看能否对应到 Foo.prototype**。 原型链如下:(重要) ![](http://img.smyhvae.com/20180306_1853.png) 注意,Object这个构造方法的显式原型是null,这是一个特例。 issues 101补充:通过原型链查找时,如果你找的是一个属性的话,则返回 undefined,如果你找的是一个方法,则报错。 ## 常见题目 - 如何准确判断一个变量是数组类型 - 写一个原型链继承的例子 - 描述 new 一个对象的过程 - zepto(或其他框架)源码中如何使用原型链 下面分别讲解。 ### 题目一:如何准确判断一个变量是数组类型 答案: ```javascript var arr1 = []; console.log(arr1 instanceof Array); //打印结果:true。 console.log(typeof arr1); //打印结果:object。提示:typeof 方法无法判断是否为数组 ``` 上方代码表明,只能通过 instanceof 来判断是否为数组。而 typeof 的打印结果是 object。 ### 题目二:写一个原型链继承的例子 来看个基础的代码: ![](http://img.smyhvae.com/20180306_1931.png) 上面这个例子是基础,但是,在回答面试官的问题时,不要写上面的例子。要写成下面这个例子:(更贴近实战) **举例:**写一个封装DOM查询的例子 > 这个例子有点像 jQuery 操作DOM节点。 表示这个例子,略难。 ### 题目三:描述 new 一个对象的过程 (1)创建一个新对象 (2)this 指向这个新对象 (3)执行代码(对this 赋值) (4)返回this 参考链接: - [原型、原型链、继承模式](https://my.oschina.net/u/2600761/blog/1524617)