JS的原型和继承,让javascript功力再上一层

  • A+
所属分类:Web前端
摘要

 没有原型的对象也是存在的: 原型方法与对象方法的优先级: 函数拥有多个长辈:

    <script>         'use strict';          // let arr = [1, 2, 3];         // let res = arr.concat(5, 6);         // console.log(res);         // 原型链         // __proto__ 上一级原型 Array(0)         // __proto__ 上上一级原型 Object           let obj1 = {};         console.log(obj1);// __proto__ 上一级原型 Object           // 获取原型         let proto1 = Object.getPrototypeOf(obj1);         console.log(obj1);           let obj2 = { name: 'cyy' };         let proto2 = Object.getPrototypeOf(obj2);         console.log(proto1 == proto2);     </script>

 

没有原型的对象也是存在的:

    <script>         'use strict';          //Object.create 指定原型和属性         // 完全的数据字典对象         let obj = Object.create(null, {             name: {                 value: 'cyy'             }         });         console.log(obj);         //hasOwnProperty是原型的方法,由于obj没有原型,因为无法使用该方法         console.log(obj.hasOwnProperty('name'));     </script>

 

原型方法与对象方法的优先级:

    <script>         'use strict';          //自己有就执行自己的,自己没有就执行原型的,原型也没有就没法执行                   // let obj = {         //     show() {         //         console.log('obj.show');         //     }         // }         // obj.__proto__.show = function () {         //     console.log('obj.__proto__.show');         // }         // obj.show();           let obj = {}         obj.__proto__.show = function () {             console.log('obj.__proto__.show');         }         obj.show();     </script>

 

函数拥有多个长辈:

1.每个对象都具有一个名为__proto__的属性;

2.每个构造函数(构造函数标准为大写开头,如Function(),Object()等等JS中自带的构造函数,以及自己创建的)都具有一个名为prototype的方法(注意:既然是方法,那么就是一个对象(JS中函数同样是对象),所以prototype同样带有__proto__属性);

3.每个对象的__proto__属性指向自身构造函数的prototype;

    <script>         'use strict';          // function user() { }         // // 打印结果         // console.log(user);         // // 打印详细结构         // console.dir(user);           // prototype和__proto__都是原型,但是使用场景不一样         // prototype是构造函数的,__proto__是对象的         function User() { }         let obj = new User();         console.log(obj.__proto__ == User.prototype);      </script>

 

原型关系详解与属性继承实例:

    <script>         'use strict';          // 系统常见构造函数 Number String Function Object         let obj = new Object();         obj.name = 'cyy';         // console.dir(obj);// 对象有__proto__属性,没有prototype属性           Object.prototype.show = function () {             console.log('Object-prototype-show');         }         // console.dir(Object);// 系统构造函数Object有__proto__属性和prototype属性           // Object.prototype的原型为null         // console.dir(Object.prototype.__proto__);           function User() { }         //构造函数User有__proto__属性和prototype属性         console.dir(User);         console.log(User.__proto__.__proto__ == User.prototype.__proto__);           let u = new User();         u.show();           User.show();     </script>

 

系统构造函数的原型体现:

    <script>         'use strict';          // let obj = {};         // console.log(obj.__proto__ == Object.prototype);           let arr = [];         console.log(arr.__proto__ == Array.prototype);         Array.prototype.show = function () {             console.log('show');         }         arr.show();           // let str = '111';         // console.log(str.__proto__ == String.prototype);           // let bool = true;         // console.log(bool.__proto__ == Boolean.prototype);           // let reg = /a/i; //new RegExp         // console.log(reg.__proto__ == RegExp.prototype);     </script>

 

自定义对象的原型设置:

    <script>         'use strict';          let child = { name: 'child' };         let parent = {             name: 'parent', show() {                 console.log('show:' + this.name);             }         };         console.log(child.__proto__ == Object.prototype);           // 设置原型         Object.setPrototypeOf(child, parent);         child.show();           // 获取原型         console.log(Object.getPrototypeOf(child));     </script>

 

原型中的constructor引用:

 

    <script>         function User(name) {             this.name = name;         }         console.dir(User);         // prototype是对象,对象的原型用__proto__获取         // 构造函数通过prototype来找原型         console.log(User.prototype.__proto__ == Object.prototype);         console.log(User.__proto__.__proto__ == Object.prototype);         // 原型通过constructor来找构造函数         console.log(User.prototype.constructor == User);         let cyy = new User.prototype.constructor('cyy');         console.log(cyy);         // __proto__只服务于对象自己本身           // 在prototype中加功能         // User.prototype.show = function() {         //     console.log(this.name);         // }         // cyy.show();           // 同时添加多个功能         User.prototype = {             constructor: User,             show1() {                 console.log('show1');             },             show2() {                 console.log('show2');             }         };         let cyy2 = new User.prototype.constructor('cyy');         console.log(cyy2);         cyy2.show1();         cyy2.show2();     </script>

 

给我一个对象还你一个世界:

    <script>         function User(name) {             this.name = name;             // this.show = function() {             //     console.log(this.name);             // }         }         let cyy = new User('cyy');         // console.log(cyy);          User.prototype = {             constructor: User,             show() {                 console.log(this.name);             }         };          function createByObject(obj, ...args) {             const constructor = Object.getPrototypeOf(obj).constructor; // 获取指定对象的构造函数             // console.log(constructor == User);             return new constructor(...args);         }         let cyy2 = createByObject(cyy, 'cyy的子对象');         console.log(cyy2);         cyy2.show();     </script>

 

总结一下原型链:

    <script>         // let arr = [];         // // arr是对象,对象只有__proto__属性         // console.log(arr.__proto__ == Array.prototype);         // console.log(arr.__proto__.__proto__ == Object.prototype);         // console.log(Object.prototype.__proto__); //null           let a = {             name: 'a'         };         let c = {             name: 'c'         };         let b = {             name: 'b',             show() {                 console.log(this.name)             }         };         Object.setPrototypeOf(a, b); //a的原型设置为b         console.log(a);         a.show();          Object.setPrototypeOf(c, b); //a的原型设置为b         console.log(c);         c.show();     </script>

 

原型链检测之instanceof:

    <script>         function A() {}          function B() {}          function C() {}          // 这里顺序很重要,先修改A的原型,再实例化A         let c = new C();         B.prototype = c;         let b = new B();         A.prototype = b;         let a = new A();          // 检测a的原型链上是否有A的prototype         console.log(a instanceof A);         console.log(a instanceof Object);         console.log(a instanceof B);         console.log(a instanceof C);         console.log(b instanceof C);     </script>

 

Object.isPortotypeOf 原型检测:

    <script>         let a = {};         let b = {};         let c = {};         Object.setPrototypeOf(b, c);         console.log(b.isPrototypeOf(a)); //b是否在a的原型链上         console.log(b.__proto__ == Object.prototype);         console.log(b.__proto__.isPrototypeOf(a));          Object.setPrototypeOf(a, b);         console.log(b.isPrototypeOf(a));         console.log(c.isPrototypeOf(a));         console.log(c.isPrototypeOf(b));     </script>

 

in与hasOwnProperty的属性差异:

        let a = {             name: 'a'         };         let b = {             age: 18         };         console.log('name' in a); //name属性是否在a对象上,或者在a的原型链上         console.log('web' in a);         Object.prototype.web = 'url';         console.log('web' in a);          console.log(a.hasOwnProperty('name')); // 检测a对象是否含有name属性,不会去检测原型链         Object.setPrototypeOf(a, b);         console.log('age' in a);         console.log(a.hasOwnProperty('age'));          for (const key in a) {             // console.log(key);              if (a.hasOwnProperty(key)) {                 console.log(key);             }         }

 

使用call或者apply借用原型链:

    <script>         // let obj = {         //     data: [11, 44, 2, 77, 2]         // };         // Object.setPrototypeOf(obj, {         //     max() {         //         return this.data.sort((a, b) => b - a)[0]; // 从大到小排序之后的数组,最大值在第一位         //     }         // });         // console.log(obj.max());          // let lessonObj = {         //     lessons: {         //         html: 3,         //         css: 58,         //         js: 88         //     },         //     //getter         //     get data() {         //         return Object.values(this.lessons);         //     }         // };         // let res = obj.max.apply(lessonObj); // 借用其他对象原型链中的方法         // console.log(res);           //没有this参数的情况         let obj = {             data: [11, 44, 2, 77, 2]         };         Object.setPrototypeOf(obj, {             max(data) {                 return data.sort((a, b) => b - a)[0]; // 从大到小排序之后的数组,最大值在第一位             }         });         console.log(obj.max(obj.data));          let lessonObj = {             lessons: {                 html: 3,                 css: 58,                 js: 88             }         };         let res = obj.max.call(null, Object.values(lessonObj.lessons)); //没有用到this,第一个参数可以设置为null         console.log(res);     </script>

 

优化方法借用:

    <script>         // console.log(Math.max(22, 55, 33));          // let obj = {         //     data: [11, 44, 2, 77, 2]         // };         // console.log(Math.max.apply(null, obj.data));          // let lessonObj = {         //     lessons: {         //         html: 3,         //         css: 58,         //         js: 88         //     }         // };         // console.log(Math.max.apply(null, Object.values(lessonObj.lessons)));           // 使用展开语法         let arr = [22, 55, 33];         console.log(Math.max(...arr));          let obj = {             data: [11, 44, 2, 77, 2]         };         console.log(Math.max.call(null, ...obj.data));          let lessonObj = {             lessons: {                 html: 3,                 css: 58,                 js: 88             }         };         console.log(Math.max.call(null, ...Object.values(lessonObj.lessons)));     </script>

 

DOM节点借用Array原型方法:

<!DOCTYPE html> <html lang="en">  <head>     <meta charset="UTF-8">     <meta name="viewport" content="width=device-width, initial-scale=1.0">     <title>demo</title> </head>  <body>     <button message="cyy1" class="red">cyy1</button>     <button message="cyy2">cyy2</button>      <script>         //array的过滤操作         let arr = [1, 2, 3, 4, 5];         console.log(arr.filter(item => item > 3));         console.dir(arr.__proto__.filter);         console.dir(Array.prototype.filter);          const btns = document.querySelectorAll('button');         // let res = Array.prototype.filter.call(btns, btn => {         //     // console.log(btn);         //     return btn.hasAttribute('class');         // });         let res = [].filter.call(btns, btn => {             // console.log(btn);             return btn.hasAttribute('class');         });         console.log(res);         console.log(res[0].innerHTML);     </script> </body>  </html>

 

合理的构造函数方法声明:

    <script>         // function User(name) {         //     this.name = name;         //     this.show = function() {         //         console.log(this.name);         //     }         // }         // let cyy1 = new User('cyy1');         // let cyy2 = new User('cyy2');         // console.dir(cyy1);         // console.dir(cyy2);         //这里的show方法写在构造函数里面,存在内存浪费,可以组合使用构造函数方法和原型方法           // function User(name) {         //     this.name = name;         // }         // User.prototype.show = function() {         //     console.log(this.name);         // }         // let cyy1 = new User('cyy1');         // let cyy2 = new User('cyy2');         // console.dir(cyy1);         // console.dir(cyy2);           // 多个方法         function User(name) {             this.name = name;         }         User.prototype = {             constructor: User,             show() {                 console.log(this.name);             }         }         let cyy1 = new User('cyy1');         let cyy2 = new User('cyy2');         console.dir(cyy1);         console.dir(cyy2);     </script>

 

this和原型没有关系的:

this与原型无关,始终指向调用原型的对象
始终指向函数运行的上下文

 
不要滥用原型:
<!DOCTYPE html> <html lang="en">  <head>     <meta charset="UTF-8">     <meta name="viewport" content="width=device-width, initial-scale=1.0">     <title>demo</title> </head>  <body>     <button onclick="this.hide()">btn</button>      <script>         // 不建议在系统的原型中追加方法         Object.prototype.hide = function() {             // console.log('hide');             this.style.display = 'none'; //this指向被点击的button         }     </script> </body>  </html>

 

Object.create与__proto__:

    <script>         // 单个对象修改原型的方法         let User = {             show() {                 console.log(this.name);             }         }           // 1、通过Object.create创建对象并指定原型         // 缺点:只能定义原型,不能获取         // let cyy = Object.create(User, {         //     name: {         //         value: 'cyy'         //     }         // });         // cyy.show();         // console.log(cyy);          // let cyy2 = Object.create(User);         // cyy2.name = 'cyy2';         // cyy2.show();           // 2、使用__proto__,可以设置原型,也可以获取原型         let cyy = {             name: 'cyy'         };         cyy.__proto__ = User;         cyy.show();         console.log(cyy.__proto__);     </script>

 

使用setPrototypeOf替代__proto__:

    <script>         // 单个对象修改原型的方法         let User = {             show() {                 console.log(this.name);             }         }          // __proto__是非标准操作         // setPrototypeOf()是标准操作          // 推荐使用Object.setPrototypeOf定义原型,使用Object.getPrototypeOf获取原型         let cyy = {             name: 'cyy'         };         Object.setPrototypeOf(cyy, User);         cyy.show();         console.dir(cyy);         console.log(Object.getPrototypeOf(cyy));     </script>

 

__proto__原来是属性访问器:

    <script>         // __proto__ getter setter         let User = {             name: 'user'         }         User.__proto__ = {             show() {                 console.log(this.name);             }         }         User.show();         console.log(User.__proto__);          User.__proto__ = 99;         // 智能判断,如果是对象,则修改原型;否则不修改         console.log(User.__proto__);           let user = {                 action: {},                 get proto() {                     return this.action;                 },                 set proto(obj) {                     if (obj instanceof Object) {                         this.action = obj;                     }                 }             }             // let obj = 99;             // user.proto = obj;             // console.log(user.proto);          let obj = {             name: 'obj'         };         user.proto = obj;         console.log(user.proto);           let obj2 = {};         console.dir(obj2);           // 如何让对象设置__proto__属性为非对象?         // 不继承Object即可         let obj3 = Object.create(null);         obj3.__proto__ = 'obj3';         console.log(obj3);     </script>

 

改变构造函数原型并不是继承:

    <script>         // 原型的继承,而不是改变构造函数的原型         // function User() {         //     this.name = function() {         //         console.log('name method');         //     }         // }         // let cyy = new User('cyy');         // console.dir(cyy);           function User() {}         User.prototype.name = function() {             console.log('User name');         }         let cyy = new User('cyy');         console.dir(cyy);           // 改变构造函数的原型         function Admin() {}         Admin.prototype = User.prototype; // 这是赋值,而不是继承。改变Admin的原型的同时,也改变了User的原型         Admin.prototype.role = function() {             console.log('admin role');         }           function Member() {}         Member.prototype = User.prototype;         Member.prototype.role = function() {             console.log('member role');         }         let m = new Member();         m.name();           let a = new Admin();         a.name();         a.role();     </script>

 

继承是原型的继承:

    <script>         // let f = {};         // console.dir(f.__proto__);         // console.log(Object.getPrototypeOf(f)); //查看原型          // 1、这种设置方式,对于顺序没有要求         // 实现原型的继承,保留本身的方法和属性,不会被覆盖和互相影响         // function User() {}         // User.prototype.name = function() {         //     console.log('User name');         // }          // // 改变构造函数的原型         // function Admin() {}         // // Admin.prototype.__proto__指向Object.prototype,就是指向null         // Admin.prototype.__proto__ = User.prototype; // 这是原型的继承         // Admin.prototype.role = function() {         //     console.log('admin role');         // }          // function Member() {}         // // Member.prototype.__proto__指向Object.prototype,就是指向null         // Member.prototype.__proto__ = User.prototype; // 这是原型的继承         // Member.prototype.role = function() {         //     console.log('member role');         // }          // let a = new Admin();         // a.role();         // let m = new Member();         // m.role();           // 2、这种设置方式,对顺序有要求         function User() {}         User.prototype.name = function() {             console.log('User name');         }          // 改变构造函数的原型         function Admin() {}         Admin.prototype.role = function() { //这个role方法在原来的Admin原型对象上,修改后就没有了             console.log('admin role');         }         Admin.prototype = Object.create(User.prototype);          function Member() {}         Member.prototype.role = function() {             console.log('member role');         }         Member.prototype = Object.create(User.prototype);          let a = new Admin();         a.role();         let m = new Member();         m.role();     </script>

 

继承对新增对象的影响:

    <script>         // 1、这种设置方式,对于顺序没有要求         // function User() {}         // User.prototype.name = function() {         //     console.log('User name');         // }          // // 改变构造函数的原型         // function Admin() {}         // let a = new Admin();          // // 改变原来原型对象的原型,就是Object.prototype的原型         // Admin.prototype.__proto__ = User.prototype;          // Admin.prototype.role = function() {         //     console.log('admin role');         // }          // a.role();            // 2、这种设置方式,对顺序有要求         function User() {}         User.prototype.name = function() {             console.log('User name');         }          // 先实例化,再改变构造函数的原型;此时对象不具有新的原型对象的方法         function Admin() {}         let a = new Admin();          Admin.prototype = Object.create(User.prototype);         Admin.prototype.role = function() { //这个role方法在原来的Admin原型对象上,修改后就没有了             console.log('admin role');         }          a.role();     </script>

 

继承对constructor属性的影响:

    <script>         // function User() {}         // let obj1 = new User();         // console.log(obj1.__proto__.constructor == User);         // let obj2 = new obj1.__proto__.constructor;         // console.log(obj2);           function User() {}         User.prototype.name = function() {             console.log('User name');         }          // 先实例化,再改变构造函数的原型;此时对象不具有新的原型对象的方法         function Admin() {}          Admin.prototype = Object.create(User.prototype); //这种方式指定原型,没有constructor         Admin.prototype.constructor = Admin; //手动指定constructor         Admin.prototype.role = function() { //这个role方法在原来的Admin原型对象上,修改后就没有了             console.log('admin role');         }          let a = new Admin();         console.log(a.__proto__);         let b = new a.__proto__.constructor;         console.log(b);     </script>

 

禁止constructor被遍历:

    <script>         function User() {}         User.prototype.name = function() {             console.log('User name');         }          function Admin() {}          Admin.prototype = Object.create(User.prototype); //这种方式指定原型,没有constructor         Object.defineProperty(Admin.prototype, 'constructor', {             value: Admin,             enumerable: false, //不允许遍历         });         console.log(Object.getOwnPropertyDescriptors(Admin.prototype));          Admin.prototype.role = function() {             console.log('admin role');         }          let a = new Admin();         for (const key in a) {             console.log(key);         }     </script>

 

方法重写与父级属性访问:

    <script>         function User() {}         User.prototype.name = function() {             console.log('User name');         }         User.prototype.age = 18;          function Admin() {}          Admin.prototype = Object.create(User.prototype); //这种方式指定原型,没有constructor         Admin.prototype.constructor = Admin;         Admin.prototype.role = function() {                 console.log('admin role');             }             // 重写父类中的方法,并使用父类中的属性         Admin.prototype.name = function() {             console.log(User.prototype.age + ' admin name');         }          let a = new Admin();         a.name();     </script>

 

面向对象的多态:

    <script>         function User() {}         User.prototype.show = function() {             this.role(); //role方法在每个子对象中实现         }          function Admin() {}         Admin.prototype = Object.create(User.prototype);         Admin.prototype.role = function() {             console.log('admin role');         }          function Member() {}         Member.prototype = Object.create(User.prototype);         Member.prototype.role = function() {             console.log('member role');         }          for (const obj of[new Admin, new Member]) {             obj.show();         }     </script>

 

使用父类构造函数初始属性:

    <script>         function User(name, age) {             this.name = name;             this.age = age;         }         User.prototype.show = function() {             console.log(this.name + this.age);         }          function Admin(...args) {             User.apply(this, args);         }         Admin.prototype = Object.create(User.prototype);          function Member(name, age) {             User.call(this, name, age);         }         Member.prototype = Object.create(User.prototype);          let a = new Admin('admin', 18);         let b = new Member('member', 20);         a.show();         b.show();     </script>

 

使用原型工厂封装继承:

    <script>         function extend(sub, sup) {             sub.prototype = Object.create(sup.prototype);             Object.defineProperty(sub.prototype, 'constructor', {                 value: sub,                 enumerable: false             });         };          function User(name, age) {             this.name = name;             this.age = age;         }         User.prototype.show = function() {             console.log(this.name + this.age);         }           function Admin(...args) {             User.apply(this, args);         }         extend(Admin, User);         let admin = new Admin('cyy', 18);         admin.show();           function Member(name, age) {             User.call(this, name, age);         }         extend(Member, User);         let member = new Member('cyy2', 22);         member.show();     </script>

 

对象工厂派生对象并实现继承:

    <script>         function User(name, age) {             this.name = name;             this.age = age;         }         User.prototype.show = function() {             console.log(this.name + this.age);         }           function admin(name, age) {             let instance = Object.create(User.prototype);             User.call(instance, name, age);             instance.info = function() {                 console.log('admin--info');             }             return instance;         }         let cyy1 = admin('cyy1', 11);         cyy1.show();         cyy1.info();     </script>

 

多继承造成的困扰: