原型和原型链

JavaScript是一个基于原型继承的,ES6的class其实也是原型继承

  • 对象具有__proto__属性,指向原型对象

  • 函数具有prototype属性,指向原型对象; 同时函数作为对象,也有__proto__属性

  • 原型对象具有constructor属性指向构造函数

  • 原型对象上的属性和方法,将被实例对象共享

  • 构造函数内的属性和方法,在实例化新对象的时候各自创建(占内存)

    Person.prototype.__proto__ == Object.prototype    // true
    Object.prototype.__proto__ == null     // true
    
    function User(){}
    User.prototype.constructor == User
    
    1
    2
    3
    4
    5

原型

原型

原型链

原型链

getPrototypeOf

  • 早期Object.create()能够指定一个对象的原型,生成一个新的实例,但是我们通过对象获取其原型对象
  • 浏览器厂商在实例上添加__proto__来实现对象访问原型的功能
  • 后来getPrototypeOfsetPrototypeOf API让我们实现获取和设置原型
const User = function(name) {
    this.name = name
}

// 直接重写构造函数的prototype,记住要制定constructor属性
User.prototype ={
    constructor: User,
    show() {
        console.log(this.name)
    }
}

let x = new User('x man');    // x man
console.log(x)

function createByObject(obj, ...args){
    //获取对象的原型,得到其对应的构造函数
    const constructor = Object.getPrototypeOf(obj).constructor;
    //通过构造函数生成与obj对象相同原型的实例
    return new constructor(...args)
}

let y = createByObject(x, "y man");
console.log(y)      // y man
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

原型方法借用

  • function.call(this, Arg, arg1, arg2, ...)
  • func.apply(thisArg, [argsArray])
  • call、bind、apply都是Function.prototype上方法
<body>
    <div>
        <button>btn1</button>
        <button>btn2</button>
        <button class="red">btn3</button>
    </div>
    <script>
        let btns = document.querySelectorAll("button");
        btns = Array.prototype.filter.call(btns, item=> item.hasAttribute('class'))
        // btns =[].filter.call(btns, item=> item.hasAttribute('class'))
        // btns =[].filter.apply(btns, [item=> item.hasAttribute('class')])
        console.log(btns)              // [button.red]
    </script>
</body>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
  • filter方法内部需要用到this,我们需要通过call或者apply将context传入
  • Math.max这种可以直接使用Math.max(1,2,3,4,2),或者Math.max.call(null,1,2,3,4)
<!--  不要这样干!!  -->
<body>
	<button onclick="this.hide()">
        Hide this btn
    </button>
</body>

<script>
	Object.prototype.hide = function() {
		this.style.display = "none"
    }
</script>
1
2
3
4
5
6
7
8
9
10
11
12

__proto__

  • 这是一个浏览器厂商实现的非标准属性

  • 该属性只能被显示设置为对象,因为Object.prototype上是getter和setter,setter限制了这个操作

  • 如果强制要把其设置为基本类型:

    const obj = Object.create(null)
    // 此时obj.__proto__为undefined
    obj.__proto__ = 1
    
    1
    2
    3

constructor

function User () {}
User.prototype.show = function(){ console.log("User show") }

function Admin () {}
Admin.prototype = Object.create(User.prototype)
Admin.prototype.role = "Admin"

User.prototype.constructor == User   // true
Admin.prototype.constructor == User  // true
1
2
3
4
5
6
7
8
9
  • Admin构造函数的原型指向User.prototype, Admin继承了User

  • AdminUser.prototype作为原型,Admin自身没有constructor属性,但是Admin.prototype能够拿到User.prototype的方法和属性,就包括了constructor

  • 直接改变原型对象的继承会丢失constructor ,需要补充上这个属性,并且这是一个不可枚举的属性,否则将会出现在for in遍历中

    for(let key in new Admin()) {
        console.log(key)  // role constructor show
    }
    
    Object.defineProperty(Admin.prototype,"constructor", {
        value: Admin,
        enumerable: false
    })
    
    for(let key in new Admin()) {
        console.log(key)  // role show
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

ES5的继承

继承是原型的继承,只要保留原来构造函数的原型,将构造函数的原型指向Target.prototype即可

  1. 构造函数的原型是个对象Teacher.prototype,这个对象的原型就是Object.prototype。当我们显示修改这个便可以实现原型的继承

    Teacher.prototype.__proto__ == Object.prototype  // true
    
    // Teacher继承Person,获得Person上的属性和方法
    Teacher.prototype.__proto__ = Person.prototype
    
    1
    2
    3
    4
  2. ✅ 通过Object.create新建具有指定原型的空对象,需要指定constructor(最好define enumerable为false)

    这时候Teacher的原型方法需要再定义,因为原来的原型已经被替换了

    Teacher.prototype = Object.create(Person.prototype)
    Teacher.prototype.constructor = Teacher
    Teacher.prototype.show = function () { 
    	// ...
    }
    
    1
    2
    3
    4
    5

in和instanceof

  • in可以判断属性是否在对象或者其原型

    let a= {name: 'hello'};
    Object.prototype.age = 24;
    
    console.log('name' in a) // true
    console.log('age' in a)  // true
    
    1
    2
    3
    4
    5
  • Object.hasOwnProperty指示对象自身属性中是否具有指定的属性

let a= {name: 'hello'};
Object.prototype.age = 24;

for(const key in a) {
    console.log("in : " + key);
    if(Object.hasOwnProperty(key)){
        console.log("own : " + key)
    }
}

// in : name
// own : name
// in : age
1
2
3
4
5
6
7
8
9
10
11
12
13

Tab构造函数

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    * {
      padding: 0;
      margin: 0;
    }

    body {
      display: flex;
      justify-content: center;
      align-items: center;
      width: 100vw;
      height: 50vh;
    }

    main {
      width: 400px;
      flex-direction: column;
      position: relative;
      margin-right: 20px;
    }

    main nav {
      display: flex;
      height: 50px;
      align-items: center;
    }

    main nav a {
      background: #95a5a6;
      margin-right: px;
      padding: 10px 20px;
      border: solid 1px #333;
      color: #fff;
      text-decoration: none;
    }

    main nav a:first-of-type {
      background: #e67e22;
    }

    section {
      height: 200px;
      width: 100%;
      background: #f1c40f;
      position: absolute;
      font-size: 5em;
      display: none;
    }

    .hd-tab section:first-of-type {
      display: block;
    }

    section:nth-child(even) {
      background: #27ae60;
    }
  </style>
</head>


<body>
  <main class="tab1">
    <nav>
      <a href="javascript:;">Tab 1</a>
      <a href="javascript:;">Tab 2</a>
    </nav>
    <section>1</section>
    <section>2</section>
  </main>
  <main class="tab2">
    <nav>
      <a href="javascript:;">TAB ONE</a>
      <a href="javascript:;">TAB TWO</a>
    </nav>
    <section>1</section>
    <section>2</section>
  </main>
</body>

<script>
  //继承工厂
  function extend(sub, sup) {
    sub.prototype = Object.create(sup.prototype);
    sub.prototype.constructor = sub;
  }

  //动作类
  function Animation() { }
  Animation.prototype.show = function () {
    this.style.display = "block";
  };
  //隐藏所有元素
  Animation.prototype.hide = function () {
    this.style.display = "none";
  };
  //必变元素集合背景
  Animation.prototype.background = function (color) {
    this.style.background = color;
  };

  //选项卡类
  function Tab(tab) {
    this.tab = tab;
    this.links = null;
    this.sections = null;
  }
  extend(Tab, Animation);
  Tab.prototype.run = function () {
    this.links = this.tab.querySelectorAll("a");
    this.sections = this.tab.querySelectorAll("section");
    this.bindEvent();
    this.action(0);
  };
  //绑定事件
  Tab.prototype.bindEvent = function () {
    this.links.forEach((el, i) => {
      el.addEventListener("click", () => {
        this.reset();
        this.action(i);
      });
    });
  };
  //点击后触发动作
  Tab.prototype.action = function (i) {
    this.background.call(this.links[i], "#e67e22");
    this.show.call(this.sections[i]);
  };
  //重置link与section
  Tab.prototype.reset = function () {
    this.links.forEach((el, i) => {
      this.background.call(el, "#95a5a6");
      this.hide.call(this.sections[i]);
    });
  };

  new Tab(document.querySelector(".tab1")).run();
  new Tab(document.querySelector(".tab2")).run();
</script>

</html>
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
上次更新: 2022/4/9 03:39:22
贡献者: Jinrui Chen, Jerry Chen