闭包、this、面向对象基础

1. 闭包

闭包定义:

  • 珠峰解释:函数执行时形成一个私有作用域,保护里面的变量不受外界干扰,这种保护机制称为闭包。
  • 市面理解:形成一个不销毁的私有作用域(私有栈内存)才是闭包

闭包应用:

1. 柯里化函数

柯里化函数思想:把多参数的函数变成单参数的函数

  1. function fn(a, b, c) {
  2. return a + b + c;
  3. }
  4. function fn1(a) {
  5. return function (b) { // 这种在函数中 return 函数的做法是市面中认为的闭包
  6. return function (c) {
  7. return a + b + c;
  8. }
  9. }
  10. }
  11. fn1(1)(2)(3);

2. 利用闭包机制隔离全局命名空间

  1. (function () {
  2. // 自执行函数执行也是闭包
  3. let a = 100; // a 是一个私有变量,不会影响全局作用域中的变量命名
  4. })();

3. 惰性封装

  1. var utils = (function () {
  2. var version = '1.0.1';
  3. function sum(a, b) {
  4. return a + b
  5. }
  6. function minus(a, b) {
  7. return a - b;
  8. }
  9. return {
  10. sum: sum,
  11. minus: minus
  12. }
  13. })();

4. 利用闭包的不销毁作用域保存数据:累加计数、选项卡闭包版本

2. this

this 是 JS 的关键字,代表当前代码执行的环境对象。一般在函数中使用,并且是在函数执行时,根据函数的不同执行方式确定不同的值。目前阶段有以下情况:

1. 事件函数中的 this 是绑定该事件的元素;

  1. let box = document.getElementById('box');
  2. box.onclick = function () {
  3. console.log(this); // box 元素对象
  4. };

2. 自执行函数中的 this 是 window

  1. (function () {
  2. console.log(this);
  3. })();

3. setTimeout/setInterval 定时器回调函数中的 this 指向 window

  1. setTimeout(function () {
  2. console.log(this);
  3. }, 0); // 定时器写 0 也不会立刻执行,也需要等待其他同步代码执行完才会执行;

4.方法调用时,看方法前面是否有点 . 如果有点前面是谁,this 就是谁,如果没有,方法中的 this 就是 window

  1. var num = 13;
  2. var obj = {
  3. num: 12,
  4. fn: function () {
  5. console.log(this.num);
  6. }
  7. };
  8. obj.fn(); // 12
  9. obj['fn'](); // 12 obj['fn'] 等效于 obj.fn 所以,this 仍然指向 obj
  10. var fn = obj.fn;
  11. fn(); // 13;window.num

6. 箭头函数中的 this 指向函数定义时所在作用域中的 this

箭头函数:

ES6 新增的语法:省略 function 关键字,在形参入口后增加 => 箭头,后面紧跟函数体;

  1. let f = (a, b) => {
  2. return a + b;
  3. console.log(this)
  4. };
  5. f();

箭头函数的简化语法:

1. 只有一个形参时,可以省略 形参入口的小括号
  1. let f2 = a => {
  2. var x = 10;
  3. x += a;
  4. return x;
  5. };

2. 如果函数只有一行代码,或者只有 return 指定返回值,可以省略函数体的花括号和 return 关键字

  1. let transfer = (a, b) => a + b;
  2. // 等效于:
  3. let transfer = function (a, b) {
  4. return a + b;
  5. }

7. 全局作用域中的 this 是 window

  1. console.log(this);

8. this 在运行时不可以赋值

  1. this = {}; // 报错

3. i 和 i

++i 和 i++ 都是给 i 累加 1,但是加的时机不同

  • ++i 是先累加自身,然后再取累加后的值和其他值运算
  • i++ 是先取当前值和其他值运算,再累加自身
  1. var i = 0;
  2. console.log(++i); // 1
  3. console.log(i++); // 0
  1. var a = 12; // 13 14
  2. var b = 13; // 14 15
  3. console.log(++a + a++ + b++ + ++b + a + b); // 13 + 13 + 13 + 15 + 14 + 15 = 83

4. 面向对象

面向过程

面向过程:以过程为核心,研究现在要解决的问题,既不考虑以前,也不考虑将来,这段代码就解决现在的问题。如果以后再有相同的功能,就再写一遍相同的代码;

面向对象

面向对象:是一种对现实世界的理解和抽象的方法。面向对象关心现在的功能分类解决,现在解决过的问题,我们过往有没有类似的代码可以复用,我们现在的写的代码能不能给将来用。

面向对象的研究范畴

  • 对象:万事万物都是对象,每个对象都具备各自的属性和功能;
  • 类:抽象事物的特性和特点,把对象分成不同的类型,例如老师类、学员类,类是一个是描述一群事物的共同特点的抽象概念;
  • 实例:类中的一个具体的个体。这个个体既然属于类,那么这个个体一定具有这个类的所有的特性和功能。
  • 例如:人类就是一个类,人类最显著的特点是可以制造并使用工具。人类的属性有语言、智慧、吃饭、睡觉…. 每个人都是人类的一个实例,每个人都有这个类型中的所有的特性和属性。

? 那么 js 的面向对象体现在哪里呢?

在公开课阶段我们强调数据类型,其中强调的就是类型。例如数组类、普通对象类,这是因为数组类和对象的属性和方法不同。 例如数组是有序的键值对,还可以 push,pop、splice 等,而对象是无序的键值对之和,而且对象不可以 pop 而 var ary = [1, 2] 是数组的一个实例,所以 ary 有所有数组的特性 而 var obj = {name: 1} 是对象的一个实例;

JS 中的内置类:

  • Object
  • Array
  • Date
  • RegExp
  • Function

这些内置类都是函数数据类型的

  1. console.log(typeof Array); // function
  2. console.log(typeof Object); // function

面向对象的研究范畴

对于面向对象要求我们掌握 封装、类的继承和多态

5. 单例模式

普通单例

在过往我们面向过程时,描述一个学员:

  1. var name = '张三';
  2. var age = 18;
  3. var sex = 'boy';

描述另一个学员:

  1. var name = '李四';
  2. var age = 19;
  3. var sex = 'girl';

这样做有一个问题,因为变量只能代表一个值,全局变量后面的会覆盖前面的。导致前面的数据丢失。 为了解决这个问题,我们现在描述一个事物,我们可以使用一个对象,因为对象是用来描述一个事物的,而对象的属性就是定性描述这个对象的特征,而属性值是定量的描述事物的这个特征。

  1. var stu1 = {
  2. name: 'zhangsan',
  3. age: 18,
  4. sex: 'boy'
  5. };
  6. var stu2 = {
  7. name: '李四',
  8. age: 19,
  9. sex: 'girl'
  10. };

像上面这样,把描述一个事物的属性放到一个对象内这种封装方式称为单例模式。
单例模式的优点是解决了全局变量互相覆盖的问题,这样 stu1 的 name 和 stu2 的 name 没有关系,因为 stu1 和 stu2 是两个不同的对象,此时 stu1 和 stu2 代表的对象叫做单例,而 stu1 和 stu2 这两个变量名称为命名空间;

高级单例

高级单例:高级单例模式不再是直接将一个对象赋值给命名空间,而是先执行一个自执行函数,在函数执行结束时返回一个对象;

  1. var person = (function () {
  2. function eat(food) {
  3. console.log(`I like eating ${food}`)
  4. }
  5. function hobby(h) {
  6. console.log(`I like playing ${h}`)
  7. }
  8. var account = '$10000000';
  9. var name = '王老五';
  10. var age = 40;
  11. return {
  12. name: name,
  13. age: age,
  14. eat: eat,
  15. hobby: hobby
  16. }
  17. })();
  • 这样写,有一个优势,我们可以在自执行函数的作用域中声明变量和函数,这个作用域不会销毁,我们可以在最后返回对象里面选择导出哪些变量和方法给外界使用,不导出的,外界拿不到;

单例模式虽然好用,但是有一个问题,有一个对象,我们就需要写一个这个对象,很繁琐。

6. 工厂模式

如何批量生产?

  1. function reg(name, age, sex) {
  2. var obj = {}; // 原材料
  3. obj.name = name; // 加工
  4. obj.age = age; // 加工
  5. obj.sex = sex; // 加工
  6. return obj; // 出厂
  7. }
  8. let s1 = reg('阿三', 19, 'boy');
  9. let s2 = reg('李四', 18, 'girl');
  10. console.log(s1 === s2); // false
  • 工厂模式:像上面这样,把实现相同供的函数封装成一个函数,当我们需要创建一个实例的时候,我们就执行这个函数即可,并且每个对象都是单例;
  • 优势:高内聚,低耦合 提高了代码的复用度

工厂模式虽然解决了批量生产的问题,但是我们说面向对象还要有类的概念,但是这种方式生产的对象都是同一类,没有分类。

  • 思考?内置的类型都有类型的概念?

js 有两种创建数据的方式,一种是字面量,另一种是实例的方式,例如:

  1. var obj = new Object();
  2. obj.name = '李四';
  3. obj.age = 19;
  4. obj.sex = 'girl';
  5. console.log(obj);

? 为什么 js 可以通过 new 操作符来生成实例?而且是有类型的?我们的可以不可以呢?

  1. var obj2 = new reg('li', 13, 'g');
  2. console.log(obj2); // 同样获取了一个实例对象,但是没有发现类型

7. 构造函数模式

前面的工厂模式已经可以批量生产了,但是还是没有我们所说的面向对象中类型的概念。为了有类型的概念,我们需要构造函数模式。

构造函数:构造函数也是一个函数,但是调用方式有别于工厂函数:

  1. 调用方式不同,构造函数只能通过 new 操作符调用;
  2. 工厂函数内需要手动创建对象实例,而构造函数模式不需要手动创建对象,在构造函数被 new 执行时,构造函数中的 this 自动和实例绑定,所以我们所有的加工都发生在 this 上;
  3. 工厂函数需要手动返回对象实例,而构造函数在被 new 操作符调用时不需要手动返回实例;

构造函数模式:

通过 new 调用一个函数,此时这个函数不再是普通函数,而是成为一个类,函数名称为类名,而通过 new 调用,自动返回这个类的一个实例。在构造函数中,我们需要抽象这个类型的属性和功能;

  1. function Teacher(name, age, subject, from) {
  2. this.name = name;
  3. this.mission = '传道受业解惑';
  4. this.age = age;
  5. this.subject = subject;
  6. this.from = from;
  7. this.teach = function () {
  8. console.log(`${name} 老师教 ${subject} 学科`);
  9. }
  10. } // Teacher 是一个类,这里类型抽象了老师的属性,一个老师有的属性有姓名,年龄,教授学科,哪个学校的老师,以及老师会讲课的功能。
  11. // 创建一个实例:
  12. let mrMa = new Teacher('马宾', 18, 'js', '珠峰培训');
  13. console.log(mrMa);
  14. let mrJiang = new Teacher('姜文', 19, 'Architect-架构师', '珠峰培训');
  15. console.log(mrJiang);
  16. console.log(typeof mrJiang); // object
  17. console.log(typeof mrMa); // object
  18. // 通过浏览器控制台查看,这两个实例(对象)的前面出现了 Teacher,此时说明 mrMa 和 mrJiang 都属于 Teacher 类的实例。

instanceof 运算符

  • 如何检测当前对象是否是当前类型的实例:

instanceof 运算符:检测对象是否是某个类型的实例,如果是 true,否则返回 false

  1. console.log(mrJiang instanceof Teacher);
  2. console.log(mrMa instanceof Teacher);
  3. console.log([] instanceof Teacher);
  4. console.log([] instanceof Object); //
  5. console.log(mrJiang instanceof Object);
  6. console.log(mrMa instanceof Object);
  • 但是因为 Object 是基类,所有实例都是对象数据类型的,所以用 instanceof 检测是否是 Object 的实例,都会返回 true;