0%

原型和原型链

构造函数大写字母开头,可以使用new操作符创建实例。

1
2
3
4
5
6
7
function Foo(name, age){
this.name=name
this.age = age
this.class = 'class-1'
// return this默认返回this实例对象
}
var f = new Foo('zhnagsan', 20)
  1. var a = {}是var a = new Object()的语法糖

  2. var a = []是var a = new Array()的语法糖

  3. function Foo(){}是var Foo = new Function(..)

  4. instanceof是用于判断引用类型属于哪个构造函数的方法。比如上述代码,f是Foo的一个实例,判断方法是f的_proto_属性,一层一层向上,能否对应到Foo的显式原型对象。f instanceof Object也是正确的,需要向上追溯两层。

  5. 所有引用类型,包括数组对象函数都可以自由扩展属性(除了null对象)

    1
    2
    3
    4
    var obj = {};obj.a = 100;
    var arr = [];arr.a = 100;
    function fn () {}
    fn.a = 100;
  6. 都有一个_proto_(隐式原型)属性,为一个普通对象

    1
    2
    3
    console.log(obj._proto_);
    console.log(arr._proto_);
    console.log(fn._proto_);
  7. 所有的函数都有一个prototype(显式原型)属性,是一个普通对象即原型对象;而原型对象有一个constructor属性,用来指定该对象的构造函数,比如M是个构造函数,那么M.prototype.constructor === M

    1
    console.log(fn.prototype)
  8. 并且所有对象的隐式原型指向构造函数的显式原型

    1
    obj._proto_ === Object.prototype
  9. 试图得到一个对象的属性时,如果这个对象没有这个属性,就到他的_proto_(构造函数的显式原型)中寻找;

  10. for in循环可以遍历对象的所有的可枚举属性,包括自身的和从构造函数原型继承的属性,有时候只想得到对象自身的属性而不是继承的属性,需要写如下代码:

    1
    2
    3
    4
    5
    var item;
    for (item in f) {
    if(f.hasOwnProperty(item)){
    console.log(item);
    }
  11. 对于如下这段代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    //构造函数
    function Foo(name, age){
    this.name = name
    };
    Foo.prototype.alertName=function(){
    alert(this.name)
    }
    //创建实例
    var f=new Foo('Zhangsam');
    f.printName=function(){
    console.log(this.name)
    }
    //测试
    f.printName();
    f.alertName();
    f.toString();

    在上面这三种情况下,究竟分别是怎样找到对应的属性呢,这里面涉及到JavaScript中的属性查找规则,正如我们第九条中所言,对于printName而言,首先在f自身中寻找printName属性,发现有这个属性,那么就停止查找了;

然后对于alertName,情况就有意思一点了,因为f自身是没有alertName这个属性的,那么此时就需要到f的隐式原型也就是_proto_对象中查找这个属性,也就是在构造函数Foo的显式原型中寻找这个属性,发现我们刚好在上面有所定义,那么就结束查找;

对于第三种情况来说,情况就更有意思的多,我们在构造函数的原型对象中也没有发现toString方法,就需要继续沿着原型链向上查找,此时就需要找到Foo的构造函数的显示原型对象,而对象的构造函数就是Object,所以我们就找到了Object的原型对象上定义的toString方法;

而JavaScript为了避免死循环,规定Object.prototype.__proto__为null,即Object.__proto__就是整个原型链的顶端。

并且Function.prototype.proto===Object.prototype,这也说明了为什么函数就是对象。
12. 构造函数的显式原型对象上的方法是被所有实例共有的

如何创建一个对象

  • 对象字面量和对象构造函数

    1
    2
    var o1 = {name: 'o1'};
    var o11 = new Object({name: 'o11'});
  • 使用显示的构造函数创建对象

    1
    2
    3
    4
    var M = function () {
    this.name = 'o2';
    }
    var o2 = new M();
  • 使用Object的create方法

    1
    2
    var p = {name: 'p'};
    var o3 = Object.create(p);

    创建的对象是用原型链连接的,o3.proto === p


instanceof的原理

instanceof的原理就是判断实例对象的隐式原型对象_proto_是不是和构造函数的prototype属性引用自同一个对象。只要是在原型链上面的构造函数对于instanceof方法来说都会返回true。

instanceof由于会追溯原型链上面的对象,所以不够严谨,我们还有一种方法是通过实例对象的_proto_对象的constructor属性的指向来判断他的构造函数,这个构造器属性就指向实例对象的构造函数。

1
2
3
function M(){};
var m = new M();
m.__proto__.constructor === M

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

利用instanceof Array

1
2
3
var arr=[];
arr instanceof Array //true
typeof arr //Object

而typeof无法判断段是否是数组


写一个原型链继承的例子

1
2
3
4
5
6
7
8
9
10
11
12
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();

描述new一个对象的过程

  1. 创建一个空对象,继承了构造函数的prototype对象
  2. 构造函数被执行。执行的时候,相应的参数被传入,同时this指向这个新实例(new Foo等同于new Foo())。
  3. 如果构造函数返回了一个“对象”,那么这个对象会取代整个new出来的结果。如果构造函数没有返回对象。那么new出来的结果是步骤1创建的对象。

下面用代码演示new运算符背后的工作原理

1
2
3
4
5
6
7
8
9
var new2 = function (func) {
var o = Object.create(func.prototype);
var k = func.call(o);
if (typeof k === 'object') {
return k;
} else {
return o;
}
}

在学习这个知识的时候,发现了一个很有意思的事情就是我们在使用new操作符去实例化一个构造函数的时候,如果构造函数明确返回一个对象,那么new构造函数才会返回那个对象;否则任何情况下都会返回实例化后的对象,这也是理解new操作符原理的关键。


zepto源码中如何使用原型链

下面是一个封装DOM查询的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function Elem(id){
this.elem = document.getElementById(id)
}
Elem.prototype.html = function(val){
var elem = this.elem;
if(val){
elem.innerHTML=val
return this; // 链式操作
}
else {
return elem.innerHTML}
}
Elem.prototype.on = function(type, fn){
var elem = this.elem;
elem.addEventListener(type, fn);
}
var div1 = new Elem('div1');
console.log(div1.html());
div1.html('<p>hello world</p>');
div1.on('click', function(){
alert('clicked');
})

2017 年 12月 31日