一、面向对象

1. oop面向对象编程

在 js 中面向类 / 实例进行程序设计,就是经典的面向对象编程

  • 类和实例的创建(构造函数模式)
  • prototype / proto ( 原型和原型链)
  • 函数的三种角色
  • 基于内置类原型拓展方法,实现链式写法
  • 借用内置类原型上的方法,实现一些特殊的需求(例如:把类数组转换为数组)
  • 细小的知识点:instanceof / constructor / hasOwnProperty…
  • 类的继承封装和多态
  • ……

2. 构造函数

  1. fucntion fn (n,m) {
  2. let total = n + m,
  3. minus = n - m;
  4. this.x = plus;
  5. this.y = minus;
  6. this.print = function () {
  7. console.log(this.x + this.y);
  8. }
  9. }
  10. // 普通函数执行
  11. fn(10,20);
  12. // 1. 形成私有的栈内存(私有作用域 scope)
  13. // 2. 形参赋值 & 变量提升 n = 10 m = 20
  14. // 3. 代码执行 this => window
  15. // 4. 没有 return 返回值
  16. // 构造函数执行
  17. let f1 = new Fn(30,10);
  18. fi.print();
  19. console.log(f1.plus); // undefined
  20. console.log(f1 instanceof Fn); // true
  21. let f2 = new Fn; // 没有传递任何实参
  22. console.log(f1.print === f2.print)
  23. // 1. 开辟一个新的私有作用域
  24. // 2. 形参赋值 & 变量提升
  25. // 3. 浏览器在当前作用域中创建一个实例对象 @a,并且让 this 指向它
  26. // 4. 代码执行 this => 当前类的实例 @a this.xxx = xxx; 都是给当前实例 @a 设置的私有属性,除此之外的私有变量等和 @a 这个实例没有必然的关系
  27. // 5. 即使我们不设置 return,浏览器也会默认把实例返回,而外面的 f1 / f2 接收的就是返回的实例,所以也说 f1 / f2 是 Fn 这个类的实例,(如果手动返回的是引用数据类型值,会以用户返回的为主,也就是返回的不再是 Fn 的实例,如果返回基本类型值,对原有的操作无影响

3. 原型:prototype 和原型链:proto

  1. 每一个函数数据类型都自带一个属性:prototype,他的属性值是一个对象
  2. prototype这个对象中有一个默认的属性:construct,存储函数本身
  3. 每一个对象都自带一个属性:proto,它的属性值是所属类的原型
  • 普通对象、数组、正则、日期、都是对象
  • 类的实例时对象:基本数据类型值虽然是所属类实例,但不是对象
  • prototype 原型属性值也是对象
  • 函数本身也是一个对象
  • JavaScript 万物皆对象

原型和原型链.png

二、函数三种角色

普通函数:闭包作用域、作用域链 构造函数:面向对象、原型链 普通对象:键值对

函数三种角色.png

三、基于内置类原型扩展方法,实现链式调用

  1. let ary = [12, 13, 13, 12, 24, 12, 12];
  2. // ary 是 Array 数组类的实例,所以可以调取 Array.prototyoe 上的方法,sort 方法中的 this 是 ary,当前要排序的数组实例(底层理解:sort 是内置方法,它可以实现排序,ary.sort(...)本意:ary 先基于 __proto__ 找到 Array.prototype 上的 sort 方法,并且把 sont 方法执行,方法中的 this 是 ary,sort 方法在执行时,会把 this 对应的数组进行排序处理)
  3. ary.sont((a, b) => a - b)
  4. // slice 执行的时候,方法中的 this 是谁,就相当于把谁克隆成一份全新的数组出来
  5. Array.prototype.slice = function () {
  6. let newAry = [];
  7. for(let i = 0; i < this.length; i++) {
  8. newAry.push(this[i]);
  9. }
  10. rerutn newAry;
  11. }
  12. let newAry = ary.slice(0);
  13. newAry = Array.prototype.slice.call(ary,0); // 等价的
  14. // 类数组转数组
  15. fucntion fn () {
  16. // arguments 类数组集合(实参集合):不是 Array 的实例,它就是一个对象而已,不能直接使用数组中的方法 =》 把类数组转换为数组
  17. // 两种方式
  18. let ary = Array.prototype.slice.call(arguments,0);
  19. ary = [].slice.call(arguments,0);
  20. // 借用数组原型上的 forEach 方法,实现给类数组进行循环(内置方法中的 this 是谁,其实当前方法就在操作谁)
  21. [].forEach.call(arguments,item => {});
  22. }
  23. fn(10,20, 30, 40, 50);

内置方法很多,但是不一定完全够用,很多时候我们需要自己向内置类的原型上拓展方法来实现一些需求

  1. ~function () {
  2. function unique () {
  3. // 注意:this 是谁就给谁去重
  4. let temp = {};
  5. for (let i = 0; i < this.length; i++) {
  6. let item = this[i];
  7. if(typeof temp[item]! == "undefined") {
  8. this[i] = this[this.length-1];
  9. this.length--;
  10. i--;
  11. continue;
  12. }
  13. temp[item] = item;
  14. }
  15. temp = mull;
  16. // 注意:返回的结果如果还是数组,则基础可以调取 Array.prototype 上的方法,实现链式调用
  17. return this;
  18. }
  19. // 往内置类原型上扩展方法,为了防止不修改原有内置的方法,我们扩展的方法名要增加对应的前缀
  20. Array.prototype.myUnique = unique;
  21. }();
  22. let ary = [12, 13, 13, 12, 24, 12, 12];
  23. ary.myUnique();
  24. array.prototype.myUnique.call(ary);

四、this问题

this 函数执行的主体:谁执行的 this 是谁和函数在哪执行和在哪定义都没关系,想要分清执行主体记住一下规律即可

  1. 给元素的某个事件绑定方法,当事件触发方法执行的时候,方法中的 this 时当前元素本身
  2. 方法执行,看方法名前面是否有点,有点,点前面是谁,this 就是谁;没有点 this 就是 window(js 在严格模式下,没有点 this 是 undefined)

“use strict” 开启严格模式

  1. 自执行函数,回调函数等方法中的 this 一般是 window
  2. 在构造函数执行过程中,构造函数体中的 this 是当前类的实例
  3. 使用 CALL / APPLY / BIND 可以改变函数中的 this 指向
  4. ES6 箭头函数中没有自己的this,所用的 this 是继承上下文中的
  1. function fn (n,m) {
  2. this.total = n + m;
  3. }
  4. let obj = {name:'OBJ'};
  5. fn(10,20); // this:window
  6. obj.fn(10, 20); // 报错:obj 中没有 fn 属性
  7. document.body.onclick = fn; // 点击 fn 后的 this:BODY
  8. document.body.onclick = function () {
  9. // this:BODY
  10. fn(30,40); // this:window
  11. };
  12. fn.call(); // this:window 不传或者传递 null / undefined 都是 window
  13. fn.call(obj,10,30); // this:obj
  14. fn.apply(obj,[10, 30]); // this:obj apply 要求传递的参数是数组
  15. document.body.onclick = fn.bind(obj,10,30); // bind 是预先处理 this,此时的 fn 还没有执行,只是把 this 改成了 obj,点击 body 的时候才执行的 =》柯里化函数(预处理机制)

1. 构造函数中的 this

  1. function Fn () {
  2. this.x = 100;
  3. this.y = 200;
  4. };
  5. fn.prototype.sum = funciton () {
  6. console.log(this.x + this.y);
  7. };
  8. let f = new Fn; // Fn 中的 this:f 当前类的实例
  9. f.sun(); // this:f
  10. Fn.prototype.sum(); // this:fn.prototype
  11. f.__proto__sum; // this:f.__proto__

es6的写法

  1. class Fn{
  2. constructor () {
  3. // this:当前 Fn 的实例
  4. this.x = 100;
  5. this.y = 200;
  6. }
  7. // 直接写的方法就是放到原型上
  8. sum() {
  9. console.log(this.x + this.y);
  10. }
  11. // static 修饰的都是把 Fn 当作普通对象设置的键值对
  12. static unique(){}
  13. }
  14. Fn.prototype.name = '珠峰';
  15. Fn.age = 10;
  16. let f = new Fn;
  17. f.sum();
  18. Fn.unique();

2. 箭头函数中 this

  1. window.name='WINDOW';
  2. let obj={
  3. name:'OBJ',
  4. fn:()=>{
  5. console.log(this.name);
  6. }
  7. };
  8. obj.fn(); // this:window
  9. obj.fn.call(obj); // this:window
  10. //==================
  11. document.body.onclick=function(){
  12. // this:BODY
  13. let _this=this;
  14. _this.count=0;
  15. /*setTimeout(function(){
  16. // this:window
  17. _this.count++;
  18. },1000);*/
  19. setTimeout(()=>{
  20. // this:没有自己的 THIS,继承下上文中的,也就是 BODY
  21. this.count++;
  22. },1000);
  23. }