# 四.原型模式

  • 创建基类的时候,简单差异化的属性放在构造函数中,消耗资源相同的功能放在基类的原型中

# 1.类图

# 2.代码

class Circle {
  x: number;
  y: number;
  radius: number;
  constructor(x: number, y: number, radius: number) {
    this.x = x;
    this.y = y;
    this.radius = radius;
  }
  clone() {
    return (<any>Object).assign({}, this);
  }
}
let _c1 = new Circle(100, 100, 5);
let _c2 = _c1.clone();
console.log(_c1, _c2);
console.log(_c1 === _c2);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//不共享
function Person(name) {
  this.name = name;
  this.getName = function() {
    console.log(this.name);
  };
}
let p1 = new Person("张三");
let p2 = new Person("李四");
console.log(p1.getName === p2.getName);

//共享一个方法
function Person(name) {
  this.name = name;
}
Person.prototype.getName = function() {
  console.log(this.name);
};
let p1 = new Person("张三");
let p2 = new Person("李四");
console.log(p1.getName === p2.getName);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function Person() {}
//是否是Object的实列
console.log(Person instanceof Object); //true
let p1 = new Person();
let obj1 = { name: "123" };
let obj1 = new Object();
object1.name = "123";
1
2
3
4
5
6
7
function sum(a, b) {
  return a + b;
}
console.log(sum(1, 2));
let sum = new Function("a", "b", "return a+b");
console.log(sum(1, 2));
1
2
3
4
5
6
function Foo() {}
let f = new Foo();
console.log(f instanceof Object);
1
2
3
function Foo() {
  this.a = 10;
}
Foo.prototype.b = 20;
let f = new Foo();
console.log(f.a, f.b);
console.log(f.hasOwnProperty("a"));
console.log(f.hasOwnProperty("b"));
1
2
3
4
5
6
7
8
function CreateJsPerson(name, age) {
  this.name = name;
  this.age = age;
}
CreateJsPerson.prototype.writeJs = function() {
  console.log("my name is " + this.name);
};
var p1 = new CreateJsPerson("sdaf", "12");
var p2 = new CreateJsPerson("sdaf", "12");
console.log(p1.writeJs === p2.writeJs); //true

//构造函数模式中拥有了类和实例的概念,并且实例和实列之间是相互独立开的-->实例识别
//基于构造函数模式的原型模式解决了 方法或者属性公有的问题-->把实列之间相同的属性和方法提取成公有的属性和方法-->想让谁公有就把他放在CreateJsPerson.prototype上即可

//1、每一个函数数据类型(普通函数、类)都有一个天生自带的属性:prototype(原型),并且这个属性是一个对象数据类型的值
//2、并且在prototype上浏览器天生给它加了一个属性constructor(构造函数),函数值是当前函数(类)本身
//3、每一个对象数据类型(普通的对象、实例、prototype...)也天生自带一个属性:__proto__,属性值是当前实列所属类的原型(prototype)
function Fn() {
  this.x = 100;
  this.sum = function() {};
}
Fn.prototype.getX = function() {
  console.log(this.x);
};
Fn.prototype.sum = function() {};
var f1 = new Fn();
var f2 = new Fn();

console.log(Fn.prototype.consturctor === Fn);

//Object是Js中所有对象数据类型的基类(最顶层的类)
//1、f1 instanceof Object --> true 因为f1通过__proto__可以向上级查找,不管多少级,最后总能找到Object
//2、在Object.prototype上没有__proto__这个属性
//3、原型链模式
//f1.hasOwnProperty("x") -->hasOwnProperty是f1的一个属性
//但是我们发现f1的私有属性上没有这个方法,那如何处理呢
//-->通过 对象名 属性名 的方法获取属性值得时候,首先在对象的私有属性上进行查找,如果私有中存在这个属性,则获取的是私有的属性值
//-->如果原型上也没有,则继续通过原型上的__proto__继续向上查找,一直找到Object.prototype为止
//这种查找的机制囧事我们的“原型链模式”

f1.getX === f2.getX; //true
f1.__proto__.getX === f2.getX; //true
f1.getX == Fn.prototype.getX; //true
f1.sum === f2.__proto__sum; //false
f1.sum === Fn.prototype.sum; //false

f1.hasOwnProperty-- > f1.__proto__.__proto__.hasOwnProperty;
//IE浏览器中,我们的原型链模式也是同样的原理,但是IE浏览器怕你通过__proto__把公有的修改,禁止我们使用__proto__

f1.sum = function() {
  //修改私有的sum
};
f1.__proto__.sum = function() {
  //修改公有的sum
};
f1.prototype.sum = function() {
  //IE下可以修改公有的sum
};

/*
 *原型链模式(扩展this和原型扩展)
 */

//在类中this.xxx=xxx;this->当前类的实列

//某一个方法中this -->看执行的时候“.”
//按照原型链查找机制查找
function Fn() {
  this.x = 100;
  this.y = 200;
  this.getY = function() {
    console.log(this.y);
  };
}
Fn.prototype = {
  consturctor: Fn,
  y: 300,
  getX: function() {
    console.log(this.x);
  },
  getY: function() {
    console.log(this.y);
  },
};
var f = new Fn();
f.getX(); //-->cosnole.log(f.x)
f.__proto__.getX(); //cosole.log(f.__proto__.x)
f.getY(); //console.log(f.y)
f.prototype.getY(); //console.log(f.prototype.y)

Array.prototype.myUnique = function() {
  var obj = {};
  for (var i = 0; i < this.length; i++) {
    var cur = this[i];
    if (obj[cur] == cur) {
      this[i] = this[this.length - 1];
      this.length--;
      i--;
      continue;
    }
    obj[cur] = cur;
  }
  obj = null;
  return this;
};
var ary = [12, 12, 12, 23, 23, 34, 12, 43, 12];
ary.myUnique().sort(function(a, b) {
  return a - b;
});

//链式写法:执行完成数组的一个方法可以紧接着执行下一个方法
//原理:
//ary为什么可以使用sort方法?-->因为sort是Array.prototype上的公有方法,而数组ary是Array这个类的一个实例,所以ary可以使用sort方法 -->数组才能使用我们Array原型上定义的属性和方法
//sort执行完成的返回值是一个排序后的"数组"
//reverse执行完成后的返回值是一个数组,可以继续执行pop
//pop执行完成返回值是被删除的那个元素,不是一个数组了,所以在执行push会报错
ary
  .sort(function(a, b) {
    return a - b;
  })
  .reverse()
  .pop();

//Array.prototype.myUnique() this-->Array.prototype

//思考题1:在数组的原型上有一个方法叫做slice,自己实现一个方法mySlice,要求和原来slice的功能一模一样
//考虑的情况:(不能使用数组内置的方法,比如添加不能使用push()而使用ary[ary.length-1]=xxx)
//slice(n,m)
//slice(n)
//slice()
//n和m是负数
//n<m是什么情况
//n和m的值超过数组的长度了
//n和m不是有效数字
//...

Array.prototype.mySlice = (function() {
  //a
})(
  //思考题2:实现一个需求
  5
)
  .push(10)
  .reduce(2); //5+10-2

Number.prototype.push = function(n) {
  return this + n;
};
Number.prototype.reduce = function(n) {
  return this - n;
};

/*
 *原型链模式(扩展-批量设置公有属性)
 */
//1、起别名
function Fn() {
  this.x = 100;
}
var pro = Fn.prototype; //把原来原型指向的地址赋值给我们的pro,现在他们操作的是同一个空间
pro.getX = function() {};
pro.getY = function() {};
pro.getZ = function() {};

//2、重构原型对象的方式
function Fn() {}
Fn.prototype = {
  consturctor: Fn,
  a: function() {},
  b: function() {},
  c: function() {},
};
var f = new Fn();
f.a();
//只有浏览器天生给Fn.prototype开辟的堆内存里面才有constructor,而我们自己开辟的这个堆内存没有这个属性,这样constructor指向就不再是Fn而是Object了
console.log(f.consturctor);
var f2 = new Fn();

//用这种方法给内置类增加公有的属性
Array.prototype.unique = function() {};

Array.prototype = {
  constructor: Array,
  unique: function() {},
};
console.log(Array.prototype);
//我们用这种方式会把之前已经存在原型上的属性和方法给替换掉,所以我们中这种方式修改内置类的话,浏览器是给屏蔽掉的
//但是我们可以一个个的修改内置方法,当我们通过下面方式在数组的原型上增加方法如果方法名和原理内置的重复了,会把人家内置的修改掉-->我们以后再内置类的原型上增加方法,要用特殊的名称

/*
 *深入扩展-原型链模式(常用的六种继承方式)
 */
Object.prototype.aa = function() {};
//for in 可以遍历私有的和原型上自定义的属性
var obj = { name: "asdfa", age: "7" };
for (key in obj) {
  console.log(obj[key]);
}
obj.aa();
obj.__proto__.aa();

//propertyIsEnumerable可枚举属性(原型上的都是不可枚举的)
for (key in obj) {
  if (obj.propertyIsEnumerable(key)) {
    console.log(obj[key]);
  }
}
//或者用这个判断是否是私有的
for (key in obj) {
  if (obj.hasOwnProperty(key)) {
    console.log(obj[key]);
  }
}

//Object.create()  方法创建一个拥有指定原型和若干个指定属性的对象,在ie6-ie8不兼容
//-->Object.create(proObj)创建一个新的对象,但是还要把proObj作为这个对象的原型

var obj = {
  constructor: Fn,
  getX: function() {},
};

//克隆一份
var obj2 = {};
for (key in obj) {
  obj2[key] = obj[key];
}

var obj3 = Object.create(obj);

//自己写一个create()
function object(obj) {
  function Fn() {}
  Fn.prototype = obj;
  return new Fn();
}
var newObj = object(obj);

/*1、原型继承 */
/* 
#div1.__proto__-->HTMLDivElement
.prototype-->HTMLElement.prototype-->Element
.prototype-->Node.prototype-->EventTarget
.prototype-->Object.prototype 
*/
function myObject() {}
myObject.prototype = {
  constructor: Object,
  hasOwnProperty: function() {},
};
function myEventTarget() {}
myEventTarget.prototype = new myObject();
myEventTarget.prototype.addEventListener = function() {};

function myNode() {}
myNode.prototype = new myEventTarget();
myNode.prototype.createElement = function() {};
var n = new myNode();
//-->原型继承 是我们js中最常用的一种继承方式
function B() {}
A.prototype = new B();
//-->子类B想要继承父类A中的所有属性和方法(私有和公有),只需要让B.prototype= new A
//-->原型继承的特点:它是把父类中私有的公有都继承到了子类原型上(子类公有)

//核心:原型继承并不是把父类中的属性和方法克隆一份一模一样的给B,而是让B和A之间增加了原型链的链接,以后B的实列n想要A中的getX方法,需要一级一级的向上查找来使用

/* call继承:把父类私有的属性和方法 克隆一份一模一样的 作为子类的私有属性*/
function A() {
  this.x = 100;
}
A.prototype.getX = function() {
  console.log(this.x);
};
function B() {
  //this -->n
  A.call(this); //-->A.call(n) 把A执行让A中的this变为了n
}
var n = new B();
console.log(n.x);

/* 冒充对象继承:把父类私有的公有的属性克隆一份一模一样的给子类当成私有的属性*/
function A() {
  this.x = 100;
}
A.prototype.getX = function() {
  console.log(this.x);
};
function B() {
  var temp = new A();
  for (var key in temp) {
    this[key] = temp[key];
  }
  temp = null;
}
var n = new B();
console.log(n.x);

/* 混合模式继承:原型继承 + call 继承 */
function A() {
  this.x = 100;
}
A.prototype.getX = function() {
  console.log(this.x);
};
function B() {
  A.call(this);
}
B.prototype = new A();
B.prototype.constructor = B;
var n = new B();
console.log(n.x);

/* 寄生组合式继承: */

function A() {
  this.x = 100;
}
A.prototype.getX = function() {
  console.log(this.x);
};
function B() {
  A.call(this);
}
B.prototype = Object.create(A.prototype);
B.prototype.constructor = B;
var n = new B();
console.log(n.x);

//ie下兼容
function objectCreate(o) {
  function fn() {}
  fn.prototype = o;
}

/* 中间类继承法 不兼容*/
//案列数组去头尾求平均数
function avgFn() {
  Array.prototype.sort.call(arguments, function(a, b) {
    return a - b;
  });
  Array.prototype.pop.call(arguments);
  Array.prototype.shift.call(arguments);
  return (
    eval(Array.prototype.join.call(arguments, "+")) / arguments.length
  ).toFixed(2);
}

function avgFn() {
  arguments.__proto__ = Array.prototype;
  arguments.sort(function(a, b) {
    return a - b;
  });
  arguments.pop();
  arguments.shift();
  return eval(arguments.join("+")) / arguments.length;
}

/*
对象:万物皆对象
类:
  div、a、document、window
实列:
1、所有的函数数据类型(普通函数、类)都天生自带一个属性:prototype,它存储的值是一个对象数据类型的值,浏览器默认为其开辟一个堆内存
2、在浏览器里给prototype默认开辟的这个堆内存上有一个默认的属性:constructor,指向当前类本身
3、每一个对象数据类型(普通的对象、数组、正则、实列、prototype)都天生自带一个属性:__proto__,指向当前实列所属类的原型
this
1、看方法执行的时候,"."前面是谁,this就是谁
2、把函数体中的this替换成分析的结果
3、按照原型链的查找模式找到对应的值即可
*/
//数组去重
Array.prototype.unique = function unique() {
  var obj = {};
  for (var i = 0; i < this.length; i++) {
    var cur = this[i];
    if (obj[cur] == cur) {
      this[i] = this[this.length - 1];
      this.length--;
      i--;
      continue;
    }
    obj[cur] = cur;
  }
  obj = null;
};

/*
思考
基于Number内置类扩展两个方法:plus、minus
(5).plus(3).minus(2) 5+3-2 = 6
*/

/* 
slice这个方法实现的非常强大,我们自己写一份mySlice实现和slice一模一样的功能(数组中现有的方法一个都不能用 )
*/

/* 原型链模式 */

//->Object.create(proObj) 创建一个新的对象,
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
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
function Foo() {}
console.log(Object.prototype.hasOwnProperty);
let a = { name: "123" };
let b = [1, 2, 3];
console.log(a);
console.log(Object.prototype.toString.call(b));
console.log(b.toString());
console.log();
1
2
3
4
5
6
7
8
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
  </head>
  <style>
    * {
      padding: 0;
      margin: 0;
    }

    canvas {
      border: 1px solid red;
    }
  </style>

  <body>
    <canvas id="canvas" width="1000" height="600"></canvas>
    <script>
      let convas = document.querySelector("#canvas");
      let ctx = canvas.getContext("2d");
      let circles = [];
      function getRandomColor() {
        let rand = Math.floor(Math.random() * 0xffffff).toString(16);
        if (rand.length == 6) {
          return "#" + rand;
        } else {
          return getRandomColor();
        }
      }
      function Circle(x, y, radius) {
        this.x = x;
        this.y = y;
        this.radius = radius;
        circles.push(this);
      }
      Circle.prototype.render = function() {
        ctx.beginPath();
        ctx.arc(this.x, this.y, this.radius, 0, 2 * Math.PI);
        ctx.fillStyle = getRandomColor();
        ctx.fill();
      };
      Circle.prototype.update = function() {
        this.radius--;
        if (this.radius > 0) return true;
      };
      canvas.onmousemove = function(event) {
        new Circle(event.clientX, event.clientY, 50);
      };
      setInterval(function() {
        ctx.clearRect(0, 0, 1000, 600);
        circles.forEach((circle) => circle.update() && circle.render());
      }, 13);
    </script>
  </body>
</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