ECMA基础(三)

原型

原型是什么?

prototype

原型prototyefunction对象的一个属性,但打印结果也是对象。

  1. function Handphone() {}
  2. console.log(Handphone.prototype); //{constructor: ƒ}
  1. function Handphone(color, brand) {
  2. this.color = color;
  3. this.brand = brand;
  4. this.screen = '18:9';
  5. this.system = 'Android';
  6. }
  7. var hp1 = new Handphone('red', 'xiaomi');
  8. var hp2 = new Handphone('black', 'huawei')
  9. console.log(hp1); //Handphone {color: "red", brand: "xiaomi", screen: "18:9", system: "Android"}
  10. console.log(hp2); //Handphone {color: "black", brand: "huawei", screen: "18:9", system: "Android"}

既然原型prototyefunction对象的一个属性,尝试下

  1. function Handphone(color, brand) {
  2. this.color = color;
  3. this.brand = brand;
  4. this.screen = '18:9';
  5. this.system = 'Android';
  6. }
  7. Handphone.prototype.rom = '64G';
  8. Handphone.prototype.ram = '6G';
  9. var hp1 = new Handphone('red', 'xiaomi');
  10. var hp2 = new Handphone('black', 'huawei')
  11. console.log(hp1.rom); //64G
  12. console.log(hp2.ram); //6G

说明

  • 原型prototype是定义构造函数构造出每个对象的公共祖先
  • 所有被该构造函数构造出的对象都可以继承原型上的属性和方法
  1. function Handphone(color, brand) {
  2. this.color = color;
  3. this.brand = brand;
  4. this.screen = '18:9';
  5. this.system = 'Android';
  6. }
  7. Handphone.prototype.rom = '64G';
  8. Handphone.prototype.ram = '6G';
  9. Handphone.prototype.screen = '16:9';
  10. var hp1 = new Handphone('red', 'xiaomi');
  11. var hp2 = new Handphone('black', 'huawei')
  12. console.log(hp1.screen); //18:9
  13. console.log(hp2.screen); //18:9

说明构造函数自身有的属性不会往祖先身上找

作用:

  • 减少代码冗余(每次实例化会重复)
  1. function Handphone(color, brand) {
  2. //配置项,需要传参取配置的
  3. this.color = color;
  4. this.brand = brand;
  5. }
  6. //公共方法
  7. Handphone.prototype.rom = '64G';
  8. Handphone.prototype.ram = '6G';
  9. Handphone.prototype.screen = '16:9';
  10. Handphone.prototype.system = 'Android';
  11. Handphone.prototype.call = function () {
  12. console.log('I am calling somebody');
  13. };
  14. var hp1 = new Handphone('red', 'xiaomi');
  15. var hp2 = new Handphone('black', 'huawei')
  16. hp2.call(); //I am calling somebody

实例化出来的对象 对原型prototype的增删改查

  1. function Test() {}
  2. Test.prototype.name = 'prototype';
  3. var test = new Test();
  4. console.log(test.name); //prototype

(不能修改原型属性,可修改实例对象属性)

  1. function Test() {}
  2. Test.prototype.name = 'prototype';
  3. var test = new Test();
  4. test.num = 1;
  5. console.log(Test.prototype); //{name: "prototype", constructor: ƒ}
  6. console.log(test); //Test {num: 1}
  7. /**
  8. * 说明test.num = 1; 写在
  9. * function Test() {
  10. * this.num = 1;
  11. * }
  12. */

(不能删除原型属性,可删除实例对象属性)

  1. function Test() {
  2. this.name = 'proto';
  3. }
  4. Test.prototype.name = 'prototype';
  5. var test = new Test();
  6. console.log(test); //Test {name: "proto"}
  7. delete test.name;
  8. console.log(Test.prototype); //{name: "prototype", constructor: ƒ}
  9. console.log(test); //Test {}

(不能更改原型属性,可更改实例对象属性)

  1. function Test() {}
  2. Test.prototype.name = 'prototype';
  3. var test = new Test();
  4. test.name = 'proto';
  5. console.log(Test.prototype); //{name: "prototype", constructor: ƒ}
  6. console.log(test); //Test {name: "proto"}

实际开发写法:

  1. //公共方法
  2. Handphone.prototype = {
  3. rom: '64G',
  4. ram: '6G',
  5. screen: '18:9',
  6. system: 'Andriod',
  7. call: function () {
  8. console.log('I am calling somebody');
  9. }
  10. }

constructor

  • prototype里面
  • 指向构造函数本身
  1. function Handphone(color, brand, system) {
  2. this.color = color;
  3. this.brand = brand;
  4. this.system = system;
  5. }
  6. console.log(Handphone.prototype);
  7. /**
  8. * 打印:
  9. * {constructor: ƒ}
  10. constructor: ƒ Handphone(color, brand, system)
  11. __proto__: Object
  12. */

说明constructor对应的是构造函数

  1. function Handphone(color, brand, system) {
  2. this.color = color;
  3. this.brand = brand;
  4. this.system = system;
  5. }
  6. function Telephone() {}
  7. Handphone.prototype = {
  8. //将constructor指向Telephone
  9. constructor: Telephone
  10. }
  11. console.log(Handphone.prototype);
  12. /**
  13. * 打印:
  14. * {constructor: ƒ}
  15. constructor: ƒ Telephone()
  16. __proto__: Object
  17. */

说明可以通过构造函数本身prototype更改contructor的指向另外的构造函数

__proto__

是实例化以后的结果,是每个实例化对象的原型prototype的容器

  1. function Car() {}
  2. Car.prototype.name = 'Benz';
  3. var car = new Car();
  4. console.log(car); //Car {}
  5. /**
  6. * 点开打印结果
  7. * Car {}
  8. * __proto__: Object
  9. */

相当于实例化以后实例对象里面的其中一个属性

  1. function Car() {
  2. var this = {
  3. __proto__: ?
  4. }
  5. }

__proto__里面存放一个对象包含构造函数原型上的属性

  1. /**
  2. * 点开打印结果
  3. * Car {}
  4. __proto__:
  5. name: "Benz"
  6. constructor: ƒ Car()
  7. __proto__: Object
  8. */
  1. function Car() {
  2. var this = {
  3. __proto__: Car.prototype
  4. }
  5. this.color = red';
  6. }

说明原型prototype是属于实例化对象而不属于构造函数

属性访问顺序

  1. function Car() {
  2. /**
  3. * var this = {
  4. __proto__: Car.prototype
  5. }
  6. */
  7. this.name = 'mazda';
  8. }
  9. Car.prototype.name = 'Benz';
  10. var car = new Car();
  11. console.log(car); //Car {name: "mazda"}

找到自身有的属性而不是找原型上的属性

尝试强行修改实例对象的__proto__的值

  1. function Person() {}
  2. Person.prototype.name = '张三';
  3. var p1 = {
  4. name: 'lisi'
  5. }
  6. var person = new Person();
  7. console.log(person.__proto__); //{name: "张三", constructor: ƒ}
  8. person.__proto__ = p1;
  9. console.log(person.__proto__); //{name: "lisi"}

说明__proto__属性可以更改

对属性的重写

  1. function Car() {}
  2. Car.prototype.name = 'Mazda';
  3. var car = new Car();
  4. Car.prototype.name = 'Benz';
  5. console.log(car.name); //Benz
  1. Car.prototype.name = 'Mazda';
  2. function Car() {}
  3. var car = new Car();
  4. Car.prototype.name = 'Benz';
  5. console.log(car.name); //Benz
  1. Car.prototype.name = 'Mazda';
  2. function Car() {}
  3. Car.prototype.name = 'Benz';
  4. var car = new Car();
  5. console.log(car.name); //Benz

注意:实例化之前的prototype和实例化的prototype是有区别的

  1. Car.prototype.name = 'Benz';
  2. function Car() {}
  3. var car = new Car();
  4. //还没实例化
  5. Car.prototype = {
  6. name: 'Mazda'
  7. }
  8. console.log(car.name); //Benz
  9. //实例化以后
  10. function Car() {
  11. var this = {
  12. __proto__: Car.prototype = {
  13. name: 'Benz'
  14. }
  15. }
  16. }
  1. Car.prototype.name = 'Benz';
  2. function Car() {}
  3. Car.prototype = {
  4. name: 'Mazda'
  5. }
  6. var car = new Car();
  7. console.log(car.name); //Mazda
  1. Car.prototype.name = 'Benz';
  2. function Car() {}
  3. var car = new Car();
  4. Car.prototype = {
  5. name: 'Mazda'
  6. }
  7. var car2 = new Car();
  8. console.log(car.name); //Benz
  9. console.log(car2.name); //Mazda

时间线

  1. Car.prototype.name = 'Benz';
  2. function Car() {};
  3. console.log(Car.prototype);
  4. var car = new Car();
  5. console.log(car);
  6. /**
  7. * 没实例化:
  8. * 打印Car.prototype结果:
  9. * {name: "Benz", constructor: ƒ}
  10. name: "Benz"
  11. constructor: ƒ Car()
  12. name: "Car"
  13. prototype: {name: "Benz", constructor: ƒ}
  14. __proto__: ƒ ()
  15. __proto__: Object
  16. */
  17. /**
  18. * 实例化后:
  19. * 打印car结果:
  20. * Car {}
  21. __proto__:
  22. name: "Benz"
  23. constructor: ƒ Car()
  24. name: "Car"
  25. prototype: {name: "Benz", constructor: ƒ}
  26. __proto__: ƒ ()
  27. __proto__: Object
  28. */

原型链

实例对象的__proto__指向构造函数的原型,所有的对象都有自己的原型(包括原型本身)

  1. function Car() {}
  2. var car = new Car();
  3. console.log(Car.prototype);
  4. console.log(car);
  5. /**
  6. * 打印Car.prototype:
  7. * {constructor: ƒ}
  8. constructor: ƒ Car()
  9. __proto__: Object
  10. */
  11. /**
  12. * 打印car:
  13. * Car {}
  14. __proto__: Object => 指向Car.prototype
  15. */

概念

沿着__proto__去找原型里的属性一层一层的继承原型的属性的这条链

  1. Professor.prototype.tSkill = 'JAVA';
  2. function Professor() {}
  3. var professor = new Professor();
  4. Teacher.prototype = professor;
  5. function Teacher() {
  6. this.mSkill = 'JS/JQ';
  7. }
  8. var teacher = new Teacher();
  9. Student.prototype = teacher;
  10. function Student() {
  11. this.pSkill = 'HTML/CSS'
  12. }
  13. var student = new Student();
  14. console.log(student);
  15. /**
  16. * 打印student
  17. * Student {pSkill: "HTML/CSS"}
  18. pSkill: "HTML/CSS"
  19. __proto__: Professor
  20. mSkill: "JS/JQ"
  21. __proto__: Professor
  22. __proto__:
  23. tSkill: "JAVA"
  24. constructor: ƒ Professor()
  25. __proto__: Object
  26. */

原型的顶端是Object.prototype(面试问题)

  1. console.log(Professor.prototype);
  2. /**
  3. * 打印Professor.prototype:
  4. * {tSkill: "JAVA", constructor: ƒ}
  5. tSkill: "JAVA"
  6. constructor: ƒ Professor()
  7. __proto__:
  8. constructor: ƒ Object()
  9. toString: ƒ toString()
  10. */

注:Object.prototype.toString()

原型链上的增删改只能是它自己本身增删改(后代不能)

引用值思考:子代赋值是否影响被继承的原型?子代赋值还是父代被赋值?

  1. function Teacher() {
  2. this.mSkill = 'JS/JQ';
  3. //引用值
  4. this.success = {
  5. alibaba: '28',
  6. tencent: '30'
  7. }
  8. }
  9. var teacher = new Teacher();
  10. Student.prototype = teacher;
  11. function Student() {
  12. this.pSkill = 'HTML/CSS'
  13. }
  14. var student = new Student();
  15. student.success.baidu = '100';
  16. console.log(teacher);
  17. //Professor {mSkill: "JS/JQ", success: {alibaba: "28", tencent: "30", baidu: "100"}}
  18. console.log(student);
  19. //Student {pSkill: "HTML/CSS"}

结果看出没有赋值到实例对象里而是赋值到实例对象的原型上

分析:子代实例对象不存在success属性,所以向上找success属性并修改值

引用值思考:

  1. function Teacher() {
  2. this.mSkill = 'JS/JQ';
  3. this.success = {
  4. tencent: '26'
  5. }
  6. }
  7. var teacher = new Teacher();
  8. Student.prototype = teacher;
  9. function Student() {
  10. this.pSkill = 'HTML/CSS'
  11. this.success = {
  12. alibaba: '26'
  13. }
  14. }
  15. var student = new Student();
  16. student.success.baidu = '100';
  17. console.log(teacher);
  18. //Professor {mSkill: "JS/JQ", success: {tencent: "26"}}
  19. console.log(student);
  20. //Student {pSkill: "HTML/CSS", uccess: {alibaba: "26", baidu: "100"}}

子代实例对象已经存在success属性只更改自己的属性值

原始值思考:

  1. function Teacher() {
  2. this.mSkill = 'JS/JQ';
  3. //原始值
  4. this.students = 500;
  5. }
  6. var teacher = new Teacher();
  7. Student.prototype = teacher;
  8. function Student() {
  9. this.pSkill = 'HTML/CSS'
  10. }
  11. var student = new Student();
  12. student.students++;
  13. console.log(teacher);
  14. //Professor {mSkill: "JS/JQ", students: 500}
  15. console.log(student);
  16. //Student {pSkill: "HTML/CSS", students: 501}
  17. //分析
  18. student.students++ => student.students = student.students += 1
  19. student原来没有students属性,但是在赋值的时候被增加了students属性

子代实例对象不能修改父代的原始值属性,隐式的为子代实例对象增加原始值属性

原始值思考:

  1. function Teacher() {
  2. this.mSkill = 'JS/JQ';
  3. this.students = 500;
  4. }
  5. var teacher = new Teacher();
  6. Student.prototype = teacher;
  7. function Student() {
  8. this.pSkill = 'HTML/CSS';
  9. }
  10. var student = new Student();
  11. student.students = '800';
  12. console.log(teacher);
  13. //Professor {mSkill: "JS/JQ", students: 500}
  14. console.log(student);
  15. //Student {pSkill: "HTML/CSS", students: "800"}

赋值的情况下会帮student实例对象新增属性students并赋值

  1. function Car() {
  2. this.brand = 'Benz';
  3. }
  4. Car.prototype = {
  5. brand: 'Mazda',
  6. intro: function () {
  7. console.log('我是' + this.brand + '车');
  8. }
  9. }
  10. var car = new Car();
  11. car.intro(); //我是Benz车 this => 实例对象
  12. Car.prototype.intro(); //我是Mazda车 this => Car.prototype

实例化对象已经存在brand,改变调用对象可以改变方法的输出

原型的原型Object.prototype

  1. function Obj() {}
  2. var obj = new Obj();
  3. console.log(obj.__proto__.__proto__);

Object.create(对象/null)创建对象用的方法

此方法可以自己定义,可以指定原型

  1. function Obj() {}
  2. Obj.prototype.num = 1;
  3. //两条语句产生的效果是一样的
  4. var obj1 = Object.create(Obj.prototype);
  5. var obj2 = new Obj();
  6. console.log(obj1);
  7. /**
  8. * 打印obj1 和 打印obj2:
  9. * Obj {}
  10. __proto__:
  11. num: 1
  12. constructor: ƒ Obj()
  13. __proto__: Object
  14. */
  1. var test = {
  2. num: 2
  3. }
  4. function Obj() {}
  5. Obj.prototype.num = 1;
  6. var obj1 = Object.create(test);
  7. var obj2 = new Obj();
  8. console.log(obj1);
  9. console.log(obj2);
  10. /**
  11. * 打印obj1
  12. * {}
  13. __proto__:
  14. num: 2
  15. __proto__: Object
  16. *
  17. * 打印obj2:
  18. * Obj {}
  19. __proto__:
  20. num: 1
  21. constructor: ƒ Obj()
  22. __proto__: Object
  23. */

Object.create()提供了一个自定义原型的功能

通过Object.create(null)创建一个空对象

  1. var obj1 = Object.create(null);
  2. console.log(obj1);
  3. //{}
  4. // No properties
  1. var obj1 = Object.create(null);
  2. console.log(obj1);
  3. obj1.num = 1;
  4. var obj2 = Object.create(obj1);
  5. console.log(obj2);
  6. console.log(obj2.num); //1
  7. /**
  8. * 打印obj2:
  9. * {}
  10. __proto__:
  11. num: 1
  12. */

作用:把其他对象作为自己的原型存在

思考:是不是所有的对象都继承Object.prototype?

  1. var obj = Object.create(null);
  2. obj.num = 1;
  3. obj.toString(); //报错,说明没有该方法
  4. console.log(obj); //这个对象并没有原型

说明通过Object.create(null)创建的对象不继承Object.prototype

不是所有的对象都继承于Object.prototype(面试题)

手动添加对象并赋值给Object.create(null)创建的对象

  1. var obj = Object.create(null);
  2. obj.num = 1;
  3. var obj1 = {
  4. count: 2
  5. }
  6. obj.__proto__ = obj1;
  7. console.log(obj);
  8. console.log(obj.count); //undefined
  9. /**
  10. * 打印obj:
  11. * {num: 1, __proto__: {…}}
  12. num: 1
  13. __proto__:
  14. count: 2
  15. __proto__: Object
  16. */

发现可以继承Object.prototype,但访问不了变量,说明自己定义的__proto__是不行的,必须是系统内置的才能访问(只能更改不能制造__proto__)

思考:undefinednull能否使用Object.prototype.toString()方法?

  1. console.log(undefined.toString()); //报错
  2. console.log(null.toString()); //报错

原始值是没有属性的,只有引用值才有

  1. var num = 1;
  2. console.log(num.toString()); //1 字符串
  3. //包装类的过程
  4. //new Number(1) -> toString();
  5. var num2 = new Number(num);
  6. console.log(num2);
  7. /**
  8. * 打印num2:
  9. * Number {1}
  10. __proto__: Number
  11. constructor: ƒ Number()
  12. toString: ƒ toString()
  13. *
  14. */

undefinednull不能经过包装类,还没有原型

  1. var num = 1;
  2. var obj = {};
  3. var obj2 = Object.create(null);
  4. //document.write() 此处有个隐式转换的操作把要打印的内部转换成string类型
  5. document.write(num); //页面显示 1
  6. document.write(obj); //页面显示 1[object Object]
  7. console.log(Object.prototype.toString(obj)); //[object Object]
  8. //obj2没有原型没法继承Object.prototype,没法转换string类型
  9. // document.write(obj2); //报错 不能转换对象为原始值 所以没法打印

思考:为什么Number()有自己的toString()方法而不是去继承对象原型的toString()方法?

包装类都有自己系统内置的toString()方法

  1. console.log(Object.prototype);
  2. console.log(Number.prototype);
  3. console.log(String.prototype);
  4. console.log(Boolean.prototype);
  5. console.log(Array.prototype);

区别:Object.prototype.toSting()Number.prototype.toSting()

  1. console.log(Object.prototype.toString.call(1));
  2. //"[object Number]" -> [对象类型的Number构造函数]
  3. console.log(Object.prototype.toString.call('a'));
  4. //"[object String]" -> [对象类型的String构造函数]
  5. console.log(Object.prototype.toString.call(true));
  6. //"[object Boolean]" -> [对象类型的Boolean构造函数]
  7. console.log(Object.prototype.toString.call([1, 2]));
  8. //"[object Array]" -> [对象类型的Array构造函数]
  9. console.log(Object.prototype.toString.call({
  10. name: 1
  11. }));
  12. // "[object Object]" -> [对象类型的Object构造函数]
  13. //说明两个toString()是不一样的,实现的功能不一样的
  14. console.log(Number.prototype.toString.call(1)); //'1'
  15. console.log(Object.prototype.toString.call(1)); //[object Number]

说明两个toString()是不一样的,实现的功能不一样的,也不是继承Object.prototype, 返回的值也不一样

因为Number.prototypeObject.prototype的返回值不一样(需求不一样),所以Number.prototype单独重写了toString()方法

继承

  1. Professor.prototype = {
  2. name: 'Mr.Zhang',
  3. tSkill: 'JAVA'
  4. }
  5. function Professor() {}
  6. var professor = new Professor();
  7. Teacher.prototype = professor;
  8. //------------------------------------------
  9. function Teacher() {
  10. this.name = 'Mr.Wang';
  11. this.mSkill = 'JS/JQ';
  12. }
  13. var teacher = new Teacher();
  14. //------------------------------------------
  15. Student.prototype = teacher;
  16. function Student() {
  17. this.name = 'Mr.Li';
  18. this.pSkill = 'HTML/CSS'
  19. }
  20. var student = new Student();
  21. console.log(student);
  22. /**
  23. * student:
  24. * Student {name: "Mr.Li", pSkill: "HTML/CSS"}
  25. name: "Mr.Li"
  26. pSkill: "HTML/CSS"
  27. __proto__:
  28. mSkill: "JS/JQ"
  29. name: "Mr.Wang"
  30. __proto__:
  31. __proto__:
  32. name: "Mr.Zhang"
  33. tSkill: "JAVA"
  34. __proto__: Object
  35. */

思考:学生是否需要继承老师和教授身上的所有的属性?

实际上并不需要

call/appl可以实现”继承”(借用属性方法)但是没办法继承原型

  1. function Teacher(name, mSkill) {
  2. this.name = name;
  3. this.mSkill = mSkill;
  4. }
  5. function Student(name, mSkill, age, major) {
  6. Teacher.apply(this, [name, mSkill]);
  7. this.age = age;
  8. this.major = major;
  9. }
  10. var student = new Student('Mr.Zhang', 'JS/JQ', 18, 'Computer');
  11. console.log(student);
  12. /**
  13. * 打印student:
  14. * Student {name: "Mr.Zhang", mSkill: "JS/JQ", age: 18, major: "Computer"}
  15. age: 18
  16. mSkill: "JS/JQ"
  17. major: "Computer"
  18. name: "Mr.Zhang"
  19. __proto__:
  20. constructor: ƒ Student(name, mSkill, age, major)
  21. __proto__: Object
  22. */

继承原型的操作

  1. function Teacher() {
  2. this.name = 'Mr.Li';
  3. this.tSkill = 'JAVA';
  4. }
  5. Teacher.prototype = {
  6. pSkill: 'JS/JQ'
  7. }
  8. var t = new Teacher();
  9. console.log(t);
  10. function Student() {
  11. this.name = 'Mr.Wang';
  12. }
  13. //继承原型
  14. Student.prototype = Teacher.prototype;
  15. var s = new Student();
  16. console.log(s);
  17. /**
  18. * 打印teacher:
  19. * Teacher {name: "Mr.Li", tSkill: "JAVA"}
  20. name: "Mr.Li"
  21. tSkill: "JAVA"
  22. __proto__:
  23. pSkill: "JS/JQ"
  24. __proto__: Object
  25. */
  26. /**
  27. * 打印student:
  28. Student {name: "Mr.Wang"}
  29. name: "Mr.Wang"
  30. __proto__:
  31. pSkill: "JS/JQ"
  32. __proto__: Object
  33. */

以上基础存在修改子代原型会影响父代的原型的问题

  1. ...
  2. //继承原型 此时赋值会影响teacher的原型
  3. Student.prototype = Teacher.prototype;
  4. Student.prototype.age = 18;
  5. ...
  6. /**
  7. * 打印teacher:
  8. * Teacher {name: "Mr.Li", tSkill: "JAVA"}
  9. name: "Mr.Li"
  10. tSkill: "JAVA"
  11. __proto__:
  12. age: 18
  13. pSkill: "JS/JQ"
  14. __proto__: Object
  15. */
  16. /**
  17. * 打印student:
  18. Student {name: "Mr.Wang"}
  19. name: "Mr.Wang"
  20. __proto__:
  21. age: 18
  22. pSkill: "JS/JQ"
  23. __proto__: Object
  24. */

圣杯模式

Buffer

思考:如何解决上述问题?

企业级方法

  1. function Teacher() {
  2. this.name = 'Mr.Li';
  3. this.tSkill = 'JAVA';
  4. }
  5. Teacher.prototype = {
  6. pSkill: 'JS/JQ'
  7. }
  8. var t = new Teacher();
  9. function Student() {
  10. this.name = 'Mr.Wang';
  11. }
  12. //企业级方案
  13. //中转构造函数--Buffer
  14. function Buffer() {}
  15. Buffer.prototype = Teacher.prototype;
  16. var buffer = new Buffer();
  17. Student.prototype = buffer;
  18. var s = new Student();
  19. console.log(s);
  20. /**
  21. * 打印s:
  22. * Student {name: "Mr.Wang"}
  23. name: "Mr.Wang"
  24. __proto__:
  25. __proto__:
  26. pSkill: "JS/JQ"
  27. __proto__: Object
  28. */

完美解决继承和隔离的问题

封装

  1. //继承方 -> Target
  2. //被继承方 -> Origin
  3. function inherit(Target, Origin) {
  4. function Buffer() {}
  5. Buffer.prototype = Origin.prototype;
  6. Target.prototype = new Buffer();
  7. //还原构造器
  8. Target.prototype.constructor = Target;
  9. //设置继承源
  10. Target.prototype.super_class = Origin;
  11. }
  12. /**
  13. * 打印s:
  14. * Student {}
  15. __proto__: Teacher
  16. constructor: ƒ Student()
  17. super_class: ƒ Teacher()
  18. __proto__: Object
  19. */

立即执行函数

IIFE-immediately invoked function expression

自动执行,执行完成以后立即销毁

作用:

  • 初始化函数

写法:

  1. ;(function(){})();
  1. ;(function(){}()); //W3C建议

传参:

  1. (function (a, b) {
  2. var a = 1,
  3. b = 2;
  4. console.log(a + b);
  5. })(1, 2);

返回值:

  1. var num = (function (a, b) {
  2. var a = 1,
  3. b = 2;
  4. return a + b;
  5. })(1, 2);
  6. console.log(num); //3

注:()里加上任何东西都为表达式

报错:函数声明写法加执行符号

  1. function test() {
  2. console.log('111');
  3. }()

正常运行:函数表达式加执行符号

  1. var test = function test() {
  2. console.log('111');
  3. }()

正常运行:函数声明写法加执行符号且传入参数

  1. function test(a) {
  2. console.log(1); //没有打印
  3. }(6);

逗号运算符返回逗号后面的数据

  1. console.log((6, 5)); //5
  2. var num = (2 - 1, 6 + 5, 24 + 1);
  3. console.log(num); //25
  • 说明一定是表达式才能被执行符号执行
  • 执行符号里面的逗号是一个运算符
  1. var test = function () {
  2. console.log(1);
  3. }
  4. console.log(test); //function(){...}
  5. var test1 = function () {
  6. console.log(2); //2
  7. }();
  8. console.log(test1); //undefined 说明销毁了

证明立即执行函数可以立即执行且执行完之后就销毁

  1. (function () {
  2. console.log(123); //123
  3. })();
  4. (function test() {
  5. console.log(123); //123
  6. })();

证明能够被执行符号执行的都为表达式,表达式会自动忽略函数名称

函数声明转换为表达式的方法:+-!, &&, ||

  1. + function test() { };
  2. - function test() { };
  3. ! function test() { };
  4. 1 && function test() { };
  5. 0 || function test() { };

循环点击案例

打印5个函数但没执行

  1. function test() {
  2. var arr = [];
  3. for (var i = 0; i < 5; i++) {
  4. arr[i] = function () {
  5. document.write(i + '');
  6. }
  7. }
  8. return arr;
  9. }
  10. var myArr = test();
  11. console.log(myArr); //[f, f, f, f, f]

继续循环执行5个函数

  1. for (var j = 0; j < 5; j++) {
  2. myArr[j]();
  3. }
  4. //5 5 5 5 5

打印结果为5个5,而不是1到5,为什么?

分析:

  1. //1. i < 5 进入循环
  2. //2. 第i项为一个声明函数但没执行函数
  3. //3. i++
  4. //4. 直到i=5无法进入循环
  5. //5. 此时return arr形成5个闭包且这时i = 5
  6. //6. 当循环j执行函数调用的时候拿到的是最后一项i的值为5
  7. //所以test函数里面arr[i]永远都为arr[5]
  1. function test() {
  2. var arr = [];
  3. var i = 0 //当i = 5 时 return arr
  4. for (; i < 5;) {
  5. arr[i] = function () {
  6. document.write(i + '');
  7. }
  8. i++
  9. }
  10. console.log(i); //5
  11. return arr;
  12. }
  13. var myArr = test();
  14. console.log(myArr); //[f, f, f, f, f]
  15. for (var j = 0; j < 5; j++) {
  16. myArr[j]();
  17. }
  18. //5 5 5 5 5

如何打印0-5

  1. function test() {
  2. var arr = [];
  3. for (var i = 0; i < 5; i++) {
  4. arr[i] = (function () {
  5. document.write(i + '');
  6. })();
  7. }
  8. return arr;
  9. }
  10. test();

继续简写

  1. function test() {
  2. for (var i = 0; i < 5; i++) {
  3. (function () {
  4. document.write(i + '');
  5. })();
  6. }
  7. }
  8. test();

写法二借助参数

  1. function test() {
  2. var arr = [];
  3. for (var i = 0; i < 5; i++) {
  4. arr[i] = function (num) {
  5. document.write(num + '');
  6. }
  7. }
  8. return arr;
  9. }
  10. var myArr = test();
  11. for (var j = 0; j < 5; j++) {
  12. myArr[j](j);
  13. }

写法三 借助立即执行函数里新增一个声明函数保存的参数

  1. function test() {
  2. var arr = [];
  3. for (var i = 0; i < 5; i++) {
  4. (function (f) {
  5. arr[f] = function () {
  6. document.write(f + '');
  7. }
  8. })(i);
  9. }
  10. return arr;
  11. }
  12. var myArr = test();
  13. for (var j = 0; j < 5; j++) {
  14. myArr[j]();
  15. }

模块化

模块化开发:闭包的形式包装圣杯模式(企业级写法)

  • 避免环境污染

  • 利于后期维护和二次开发

  1. var inherit = (function () {
  2. var Buffer = function () {}
  3. return function (Target, Origin) {
  4. Buffer.prototype = Origin.prototype;
  5. Target.prototype = new Buffer();
  6. Target.prototype.constructor = Target;
  7. Target.prototype.super_class = Origin;
  8. }
  9. })();
  10. Teacher.prototype.name = 'Mr.Zhang';
  11. function Teacher() {}
  12. function Student() {}
  13. var s = new Student;
  14. var t = new Teacher;
  15. console.log(s);
  16. console.log(t);
  17. inherit(Student, Teacher)

案例:

  1. var inherit = (function () {
  2. var Buffer = function () {}
  3. return function (Target, Origin) {
  4. Buffer.prototype = Origin.prototype;
  5. Target.prototype = new Buffer();
  6. Target.prototype.constructor = Target;
  7. Target.prototype.super_class = Origin;
  8. }
  9. })();
  10. //创建一个独立的空间让其自启动
  11. var initProgrammer = (function () {
  12. //父级构造函数
  13. var Programmer = function () {}
  14. //父级原型增加属性和方法
  15. Programmer.prototype = {
  16. name: '程序员',
  17. tool: '计算机',
  18. work: ' 编写英语程序',
  19. duration: '10个小时',
  20. say: function () {
  21. console.log('我是一名' +
  22. this.myName +
  23. this.name +
  24. ', 我的工作是用' +
  25. this.tool +
  26. this.work +
  27. '我每天工作' +
  28. this.duration +
  29. '我的工作需要用到' +
  30. this.lang.toString() +
  31. '。');
  32. }
  33. }
  34. //子级构造函数
  35. //实例化以后可以访问父级原型的属性和方法
  36. function FrontEnd() {}
  37. function BackEnd() {}
  38. //继承父级
  39. inherit(FrontEnd, Programmer);
  40. inherit(BackEnd, Programmer);
  41. //子级原型上新增属性
  42. FrontEnd.prototype.lang = ['HTML', 'CSS', 'JavaScript'];
  43. FrontEnd.prototype.myName = '前端';
  44. BackEnd.prototype.lang = ['JAVA', 'Node', 'SQL'];
  45. BackEnd.prototype.myName = '后端';
  46. //子级构造函数返回出去
  47. //return外面必须要有变量接收返回值
  48. return {
  49. FrontEnd: FrontEnd,
  50. BackEnd: BackEnd
  51. }
  52. })();
  53. var frontEnd = new initProgrammer.FrontEnd();
  54. var bacnEnd = new initProgrammer.BackEnd();
  55. frontEnd.say();
  56. //我是一名前端程序员, 我的工作是用计算机 编写英语程序我每天工作10个小时我的工作需要用到HTML,CSS,JavaScript。
  57. bacnEnd.say();
  58. //我是一名后端程序员, 我的工作是用计算机 编写英语程序我每天工作10个小时我的工作需要用到JAVA,Node,SQL。

关于企业级协助开发

  1. window.onload = function () {
  2. init();
  3. }
  4. function init() {
  5. initCompute();
  6. initFunctions();
  7. }
  8. var initCompute = (function () {
  9. var a = 1,
  10. b = 2;
  11. function add() {
  12. console.log(a + b);
  13. }
  14. function minus() {
  15. console.log(a - b);
  16. }
  17. function mul() {
  18. console.log(a * b);
  19. }
  20. function div() {
  21. console.log(a / b);
  22. }
  23. //抛出的函数是需要执行的
  24. return function () {
  25. add();
  26. minus();
  27. mul();
  28. div();
  29. }
  30. })();
  31. var initFunctions = (function () {})();

插件开发

函数表达式变量接收立即执行函数的闭包

  1. var add = (function test() {
  2. var a = 1;
  3. function add() {
  4. a++;
  5. console.log(a);
  6. }
  7. return add;
  8. })();
  9. add(); //2
  10. add(); //3

利用全局变量保存立即执行函数的闭包

  1. (function test() {
  2. var a = 1;
  3. function add() {
  4. a++;
  5. console.log(a);
  6. }
  7. window.add = add;
  8. })();
  9. add(); //2
  10. add(); //3

插件化写法

  1. (function () {
  2. function Test() { }
  3. window.Test = Test;
  4. })();
  5. var test = new Test();