JS原型、原型链、包装类和隐式转换

57 阅读4分钟

(原型和原型链基础)

目录

原型基础

更改原型

原型和原型链

包装类和隐式转换

课后作业

一、原型基础

  • 用构造函数来讲,原型就是function对象的一个属性,打印出来会看到其实原型也是一个对象
  • prototype是定义构造函数构造出的每个对象的公共祖先
  • 所有被该构造函数构造出的对象都可以继承原型上的属性和方法
  • 自己有的属性不会从原型上找
  • 一般需要配置的属性会写到构造函数内(传参),方法和写死的属性会放到原型里
 ​
 // 原型prototype其实是function对象的一个属性
 //这个prototype是定义构造函数构造出的每个对象的公共祖先
 ​
 function HandPhone(color, brand) {
   this.color = color;
   this.brand = brand;
   this.screen = "18:9";
   this.system = "Android";
 }
 ​
 // console.log(HandPhone.prototype);
 // 打印出来结果它也是对象
 ​
 HandPhone.prototype.rom = "64G";
 HandPhone.prototype.ram = "6G";
 ​
 let hp1 = new HandPhone("red", "小米");
 let hp2 = new HandPhone("black", "华为");
 ​
 console.log(hp1.rom); // 64G
 console.log(hp2.ram); // 6G
 ​
 ​
 ​
 function Test() {}
 Test.prototype.name = "prototype";
 let test = new Test();
 console.log(test.name);
 function Test() {}
 Test.prototype.name = "prototype";
 let test = new Test();
 console.log(test.name); // 'prototype'

二、更改原型

 // 可以给原型增加属性
 function Car() {
   let this = {
     __proto__: (Car.prototype = {
       name: "Benz",
     }),
   };
 }
 ​
  function Car(){
   this:{
     __proto__: Car.prototype {
       name:
     }
   }
 } 
 // Car.prototype.constructor -> Car() -> prototype -> name:Benz -> mazda
  • 不能通过被构造函数构造出的实例化对象更改原型
  • 只能通过实例化对象访问到原型属性和方法

protoprototype

  • __proto__是存prototype对象地址的地方,在new了一个实例之后,这个实例里会有__proto__属性
  • prototype是这个实例的祖先元素合集,它也是一个对象,里面存的东西就是这个实例的通用属性
  • constructor是在prototype里的一个属性,它默认会指向构造函数本身
  • __proto__constructor可以更改为其他对象的地址

windowreturn

 // window return 的问题
 function test() {
   let a = 1;
   function plus1() {
     a++;
     console.log(a);
   }
   return plus1;
 }
 let plus = test();
 plus();
 plus();
 plus();
 // 插件的写法
 (function () {
   function Test() {}
   window.Test = Test;
 })(); // ES5这样不会污染其他的东西,隔离了全局作用域

三、原型和原型链

  • 对象,除了把它构造出来的函数有原型,构造函数的原型都还有原型,且这个对象能继承它原型的原型的属性,这叫做原型链
  • 所有的对象都有原型,包括原型本身

原型链

  • 沿着__proto__去找原型里的属性,一层一层去继承属性的链条
  • 用实例来说明原型链
 // 原型链
 Professor.prototype.tSkill = "JAVA";
 function Professor() {}
 let professor = new Professor();
 Teacher.prototype = professor;
 function Teacher() {
   this.mSkill = "JS/JQ";
   this.students = 500;
   this.success = {
     alibaba: "28",
     tencent: "30",
   };
 }
 let teacher = new Teacher();
 Student.prototype = teacher;
 function Student() {
   this.pSkill = "HTML/CSS";
 }
 let student = new Student();
 console.log(Professor.prototype);
 // 可以一层一层往上继承
 student.success.baidu = "100";
 console.log(teacher, student);
 //可以改或增加teacher.prototype的引用值属性,但不推荐这么做
 student.students++;
 console.log(student, teacher);
 // student501, teacher不变 原始值不能被子代修改
  • 原型链的终点是Object.prototype,它的__proto__null
  • 后代不能对上层的prototype进行修改,只能访问

Object.create()

 // 创建obj1空对象
 let obj1 = Object.create(null);
 console.log(obj1);
 obj1.num = 1;
 let obj2 = Object.create(obj1);
 console.log(obj2);
  • Object.create()可以指定创建对象的原型,和new一个实例化是一样的
  • 如果用Object.create()指定的原型是null那么这个对象是一个什么都没有的空对象,原型也没有
  • 如果用Object.create()指定原型,那这个对象可能不继承于Object.prototype(面试题)

四、包装类和隐式转换

 //包装类 隐式转换
 let num = 1;
 let obj = {};
 let obj2 = Object.create(null);
 document.write(num); // 页面出现1
 document.write(obj); // 页面出现[object object]
 document.write(obj2); // 报错
 // 因为obj的原型有转换成原始值的方法而obj2没有原型所以不能隐式转换
  • 上面的obj能被页面渲染出来是因为obj这个对象继承原型的toString()方法,js会包装类,隐式执行toString()方法
  • obj2不能被页面渲染就是因为obj2的原型是空

重写方法

 //重写方法
 Number.prototype.toString.call(1); // '1'
 Object.prototype.toString.call(1); // [object Number]
 // 证明Number的toString方法和Object.prototype的toString方法不是一个东西
 // 因为Object的这个方法不能满足Number的使用所以在Number的原型里重写了toString方法

call/apply 更改this指向

 // call/apply 更改this的指向
 function test() {
   console.log("a");
 }
 test.call(); // 'a '
 function Car(brand, color) {
   this.brand = brand;
   this.color = color;
 }
 let newCar = {};
 Car.call(newCar, "Benz", "red");
 console.log(newCar);
 //call把newCar指定为Car里的this然后把实参传进去
 Car.apply(newCar, ["Benz", "red"]);
 // apply第二个参数是数组里面是实参等于arguments

练习题

 //练习题
 function Compute() {
   this.plus = function (a, b) {
     console.log(a + b);
   };
   this.minus = function (a, b) {
     console.log(a - b);
   };
 }
 function FullCompute() {
   Compute.apply(this);
   this.mul = function (a, b) {
     console.log(a * b);
   };
   this.div = function (a, b) {
     console.log(a / b);
   };
 }
 let compute = new FullCompute();

 //这样 compute被FullCompute构造后拥有加减乘除所有方法

五、课后作业

  • 写一个插件,任意传两个数字,调用插件内部方法可进行加减乘除功能
 //写一个插件,任意传两个数字,调用插件内部方法可进行加减乘除功能
 (function () {
   function Compute(opt) {
     this.one = opt.one;
     this.two = opt.two;
   }
   Compute.prototype = {
     plus() {
       console.log(this.one + this.two);
     },
     minus() {
       console.log(this.one - this.two);
     },
     mul() {
       console.log(this.one * this.two);
     },
     div() {
       console.log(this.one / this.two);
     },
   };
   window.Compute = Compute;
 })();
 let num = new Compute({
   one: 4,
   two: 2,
 });
 ​
 console.log(num.plus()); // 6
 console.log(num.minus()); // 2
 console.log(num.mul()); // 8
 console.log(num.div()); //
  • 练习题2
 //年龄为多少岁,姓名为xx,买了一辆排量为xx的什么颜色的什么牌子的车
 //利用call 和apply
 function Car(opt) {
   this.brand = opt.brand;
   this.color = opt.color;
   this.power = opt.power;
   this.info = function () {
     return (
       "品牌为" +
       this.brand +
       ",颜色为" +
       this.color +
       ",排量为" +
       this.power +
       "的车"
     );
   };
 }
 function Person(opt) {
   this.car = opt.car;
   Car.call(this, this.car);
   this.age = opt.age;
   this.name = opt.name;
   this.income = opt.income;
   this.say = function () {
     console.log(
       "名字为" +
         this.name +
         ",年龄为" +
         this.age +
         ",收入为" +
         this.income +
         "的人买了一辆" +
         this.info()
     );
   };
 }
 let bai = new Person({
   name: "白境",
   age: 20,
   income: "20k",
   car: {
     brand: "Benz",
     color: "white",
     power: "3.0",
   },
 });
 bai.say(); ;// 名字为白境,年龄为20,收入为20k的人买了一辆品牌为Benz,颜色为white,排量为3