关于构造函数和原型链运行机制的试题与知识点

题目

  • 如何准确判断一个变量是数组类型
  • 写一个原型链继承的例子
  • 描述new一个对象的过程
  • zepto(或其他框架)源码中如何使用原型链

知识点

1. 构造函数

  • 构造函数要用大写字母开头
  • var a=其实是var a=new Object)的语法糖
  • var a=[]其实是var a=new Array)的语法糖
  • function Foo){..}其实是var Foo=new Function(.)
  • 使用instanceof 可以判断一个函数是否是一个变量的构造函数

a71efaafly1g2rc6awjmxj20mf0dzwhx.jpg

2. 原型规则和示例

  • 所有的引用类型(数组、对象、函数),都具有对象特性,即可自由扩展属性(除了“null”意外)
  • 所有的引用类型(数组、对象、函数),都有一个proto(隐式原型)属性,属性值是一个普通的对象
  • 所有的函数,都有一个prototype(显式原型)属性,属性值也是一个普通的对象
  • 所有的引用类型(数组、对象、函数),_proto_属性值指向它的构造函数的”prototype”属性值

a71efaafly1g2rchgklygj20mg0e4dk5.jpg

  • 当试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,那么会去它的proto(即它的构造函数的prototype)中寻找。

    我们在一个构造函数的显式原型上去定义方法可以有效的减少内存占用,因为如果我们定义在构造函数内部,则每实例化一个对象,就会开辟一个堆内存去存放挂载到其实例上,然而对于方法的调用这是没有必要的

关于 prototype 它有以下几个要点,务必牢记:

  1. 每一个函数(类)都有原型属性,称作prototype,这个属性提供了可供当前类的实例调用的属性和方法。
  2. 浏览器默认给原型开辟的堆内存中有一个constructor属性,这个属性存放的是函数本身
  3. 每一个对象的实例上都有一个proto属性称为原型链,这个属性指向当前类的所属原型,不确定的原型都指向Object.prototype,然而Object的proto指向null

prototype下的name属性指函数名,length属性指传入的形参的个数

a71efaafly1g2rcm3gp5cj20me0e377t.jpg

利用 for in 循环可以来获取对象身上自己定义的属性而不获取来自原型的属性

a71efaafly1g2rcrpqiwvj20mk0e0tbu.jpg

3. 原型链

当一个方法在原型上没有时,就会查找原型链

a71efaafly1g2rcwekrs7j20mj0du0vy.jpg
a71efaafly1g2rd0r86dzj20mm0drmyo.jpg

4. intanceof

intanceof 用于判断 引用类型 属于哪个 构造函数 的方法。

finstanceofFoo的判断逻辑是:

  1. f的proto一层一层往上,能否对应到Foo.prototype,只要 f.__proto__ == Foo.prototype 就验证通过
  2. 再试着判断 f instanceof Object
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function Foo(name) {
this.name = name
}
function Foo2() { }

var f = new Foo('蔡徐坤');

// 让Foo2的prototype指向Foo的prototype,这时候,Foo2与Foo的prototype可以看作为一个对象,也就是说修改Foo的prototype相当于修改Foo1的prototype,反之亦然
Foo2.prototype = Foo.prototype;
Foo.prototype.age = 'unknown';
Foo2.prototype.hobbies = '唱、跳、篮球、Rap';

// 由下可以看出Foo2与Foo的prototype指向同一个对象
console.log(Foo.prototype); // Foo { age: 'unknown', hobbies: '唱、跳、篮球、Rap' }
console.log(Foo2.prototype); //Foo { age: 'unknown', hobbies: '唱、跳、篮球、Rap' }

// 由于实例 f 的 __proto__ 指向 Foo 的 prototype ,而Foo的 prototype 与 Foo2 的 prototype 是一个,所以 f 既属于 Foo 又属于 Foo2
console.log(f instanceof Foo); // true
console.log(f instanceof Foo2); // true

5. 关于原型重定向问题

先看下面的一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function fun(){
this.a = 0;
this.b = function(){
alert(this.a);
}
}
fun.prototype = {
b: function(){
this.a = 20;
alert(this.a);
},
c: function(){
this.a = 30;
alert(this.a);
}
}
var my_fun = new fun();
my_fun.b(); // 0
my_fun.c(); // this => my_fun.a = 30 ; 30

结果:0 30

my_fun.a 用来设置私有属性

my_fun.__proto__.a 用来设置公有属性

原型重定向导致的问题:

  1. 自己开辟的堆内存中没有constructor属性,导致类的原型构造函数缺失(解决:自己手动在堆内存中增加constructor属性)
  2. 当原型重定向后,浏览器默认开辟的那个类原型堆内存会被释放掉,如果之前已经存储了一些方法或属性,都会丢失(所以:内置累的原型不允许重定向到自己开辟的堆内存,因为内置类的原型上存在很多属性方法,重定向后都没了,这样是不被允许的;但浏览器对内置类有保护机制)
  3. 当我们需要给类的原型批量设置属性和方法的时候,一般都是让原型重定向到自己创建的对象中

解题

1. 如何准确判断一个变量是数组类型

1
2
3
var arr = [];
console.log(arr instanceof Array); // true
console.log(typeof arr); // object 不能用typeof判断一个变量是否是数组类型

2. 写一个原型链继承的例子

基础实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//动物
function Animal(){
this.eat = function(){
console.Log('animal eat')
}
}
//狗
function Dog(){
this.bark = function(){
console.Log('dog bark')
}
}
Dog.prototype = new Animal()
//哈士奇
var hashiqi = new Dog()
//接下里代码演示时,会推荐更加贴近实战的原型继承示例!

封装DOM查询:戳我查看完整示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function Elem(id) {
this.elem = document.getElementById(id);
}

Elem.prototype.html = function (html) {
if (html == null) {
return this.elem.innerHTML;
} else {
this.elem.innerHTML = html;
return this; // 返回this,便于链式操作
}
}

Elem.prototype.on = function (eventType, fn) {
if (eventType != null && fn != null) {
this.elem.addEventListener(eventType, fn);
return this;
} else {
throw new Error('请传入“事件类型”,“执行方法”!');
}
}

var div = new Elem('div');
div.on('click', function(){
alert(div.html());
})

3. 描述new一个对象的过程

  • 创建一个新对象
  • 对新对象执行 [[prototype]] 连接
  • this 指向这个新对象
  • 执行代码,即对this 赋值
  • 返回 this (这一步是默认的)