常见概念
-
构造函数
-
构造函数-扩展
-
原型规则和示例
-
原型链
-
instanceof
构造函数
任何一个函数都可以被 new,new 了之后,就成了构造方法。
如下:
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 中的普通函数与构造函数
当 new 之后,this 会先变成一个空对象,然后通过this.name = name
来赋值。
构造函数的扩展
上图中发现,数组、对象、函数也有构造函数,它们的构造函数是 Array、Object、function。实际开发中,都推荐前面的书写方式。
原型规则
原型规则是学习原型链的基础。原型规则有五条,下面来讲解。
规则 1
所有的引用类型(数组、对象、函数),都具有对象特性,都可以自由扩展属性。null 除外。
举例:
规则 2
所有的引用类型(数组、对象、函数),都有一个_proto_
属性,属性值是一个普通的对象。_proto_
的含义是隐式原型。
其实,规则 2 是规则 1 的特例,只不过,js 语法帮我们自动加了 规则 2。
规则三
所有的函数(不包括数组、对象),都有一个prototype
属性,属性值是一个普通的对象。prototype
的含义是显式原型。(实例没有这个属性)
规则四
所有的引用类型(数组、对象、函数),_proto_
属性指向它的构造函数的prototype
值。
总结:以上四条,要先理解清楚,然后再来看下面的第五条。
规则五
当试图获取一个对象的某个属性时,如果这个对象本身没有这个属性,那么会去它的_proto_
中寻找(即它的构造函数的prototype
)。
举例代码1
:
//创建方法function Foo(name) {this.name = name;}Foo.prototype.alertName = function () {// 既然 Foo.prototype 是普通的对象,那也允许给它添加额外的属性 alertNameconsole.log(this.name);};var fn = new Foo('smyhvae');fn.printName = function () {console.log(this.name);};// 测试fn.printName(); //输出结果:smyhvaefn.alertName(); //输出结果:smyhvae
上方代码中,虽然 alertName 不是 fn 自身的属性,但是会从它的构造函数的prototype
里面找。
扩展:遍历循环对象自身的属性
我们知道,for in
循环可以遍历对象。针对上面的那个 fn 对象,它自身有两个属性:name
、printName
,另外从原型中找到了第三个属性alertName
。现在,如果我们对 fn 进行遍历,能遍历到两个属性还是三个属性呢?
答案:两个。因为,高级浏览器中已经在 for in
循环中屏蔽了来自原型的属性。但是,为了保证代码的健壮性,我们最好自己加上判断,手动将第三个属性屏蔽掉:
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
格式:
对象 instanceof 构造函数;
instanceof
的作用:用于判断引用类型属于哪个构造函数。
例 1:判断一个变量是否为数组: 变量 instanceof Array
例 2:
function Person() {}//p--->Person.prototype--->Object.prototype--->nullvar p = new Person();//构造函数的**原型**是否在 p 对象的原型链上!console.log(p instanceof Person);
例 3:
fn instanceof Foo;
上面这句话,判断逻辑是:fn 的_proto_
一层一层往上找,看能否对应到 Foo.prototype。
原型链如下:(重要)
注意,Object 这个构造方法的显式原型是 null,这是一个特例。
issues 101 补充:通过原型链查找时,如果你找的是一个属性的话,则返回 undefined,如果你找的是一个方法,则报错。
常见题目
-
如何准确判断一个变量是数组类型
-
写一个原型链继承的例子
-
描述 new 一个对象的过程
-
zepto(或其他框架)源码中如何使用原型链
下面分别讲解。
题目一:如何准确判断一个变量是数组类型
答案:
var arr1 = [];console.log(arr1 instanceof Array); //打印结果:true。console.log(typeof arr1); //打印结果:object。提示:typeof 方法无法判断是否为数组
上方代码表明,只能通过 instanceof 来判断是否为数组。而 typeof 的打印结果是 object。
题目二:写一个原型链继承的例子
来看个基础的代码:
上面这个例子是基础,但是,在回答面试官的问题时,不要写上面的例子。要写成下面这个例子:(更贴近实战)
function DomElement(id) {this.dom = document.getElementById(id); } DomElement.prototype.html = function (val) {var ele = this.dom;if (val) {ele.innerHTML = val;return this;} else {return ele.innerHTML;} }; DomElement.prototype.on = function (type, fn) {var ele = this.dom;ele.addEventListener(type, fn);return this; }; var div1 = new DomElement('div1'); div1.html('<p>这是一段文字</p >'); div1.on('click', function () {console.log('clicked'); });
题目三:描述 new 一个对象的过程
(1)创建一个新对象
(2)this 指向这个新对象
(3)执行代码(对 this 赋值)
(4)返回 this
参考链接:
-
原型、原型链、继承模式