2021-04-09

JKJFbtsTA1CM1LVNEM0INI

8259

=============================================================================

正课: 面向对象

1. 什么是面向对象

2. 封装

3. 继承

一. 什么是面向对象:

1. 什么是对象:

(1). 用途: 程序中描述现实中一个具体事物的属性和功能的程序结构

(2). 本质: 内存中一块集中存储多个属性和功能的存储空间,再起一个名字

2. 为什么: 为了大量数据的管理和维护

3. 何时: 今后所有程序因为不可能只维护一样东西,都是要维护很多数据,所以都要用面向对象来开发。

4. 如何: 面相对象三大特点: 封装,继承,多态

二. 封装:

1. 什么是: 将一个事物的属性值和功能集中存储到一个对象中保存。

2. 为什么: 为了大量数据的管理和维护

3. 何时: 只要使用面向对象开发,都要先封装对象,再按需使用对象中的成员

4. 如何创建对象: 3种:

(1). 只创建一个对象:

a. 如何: var 对象名={

属性名: 属性值,

... : ... ,

方法名: function(){

... ...

}

}

b. 问题: 对象内自己的函数中,想访问对象自己的属性,如果什么前缀也不加,报not defined错误!找不到!

  因为: 任何函数默认只能在自己的好友列表中查找变量使用。虽然方法是在对象内创建,但是对象的{}不是一级作用域,所以对象中方法的好友列表中是不包含对象自己的属性的。所以,方法内无法直接使用属性名来访问属性。

c. 不好的解决: 在方法中写死"对象名.属性名"

  为什么: 紧耦合,一旦对象名改变,而忘记修改内部的对象名,就会出错!必须内外对象名同时修改,保持一致,才能正确执行。

d. 好的解决: this

1). 什么是: 当函数调用时,自动生成的,指向正在调用函数的.前的对象 的关键词

2). 为什么: 对象自己的方法都无法直接访问自己的属性

3). 何时: 只要对象自己的方法中要使用自己的成员时,都要用this!

4). 原理: this会自动指向正在调用该函数的.前的对象

5). 鄙视: this和定义在哪儿毫无关系!只和谁在调用函数有关!只有在函数调用时,才能确定this!

(2). 如果在创建对象时,暂时不知道对象的内容,可以分两步创建对象

a. 先创建一个空对象:  var obj={}; //new Object()

      new和(),只要写任意一个就行。但是不能都省略!

b. 再添加新属性: 今后,为一个已有对象中添加新属性,都是用强行赋值的方式添加:

  obj.属性名=新值

  obj.方法名=function(){ ... }

      js中为一个不存在的属性强行赋值,不但不报错,还会自动创建该属性。

      揭示了: 一切对象底层其实都是关联数组

      1). 都有下标可以访问每个成员:

          arr["下标名"] => 都可简写为: arr.下标名

          arr.下标名 会被自动翻译为标准写法: arr["下标名"]

          obj["属性名"] => 都可简写为: obj.属性名

          obj.属性名会被自动翻译为标准写法: obj["属性名"]

          何时使用[],何时使用.?

          如果属性名是写死的,首选.

          如果属性名是需要动态从变量获得,就必须用[],还不能加""

      2). 访问一个不存在的下标位置,都不会报错,而是返回undefined

      3). 都可随时向一个不存在的位置强行赋值,来自动添加该新元素。

      4). 都可用for in循环遍历,不能用for循环遍历

(3). 反复创建多个相同结构的对象: 用构造函数:

a. 什么是构造函数: 专门定义同一类型的所有对象的同一结构的函数

b. 何时: 反复创建多个相同结构的对象,都要用构造函数

c. 为什么: 代码重用,重用结构

d. 如何: 2步:

1). 定义构造函数来定义同一类型多个对象的同一成员结构

function 类型名(形参列表){

this.属性名=形参;

              ... = ... ;

            this.方法名=function(){

... this.属性名 ...

}

        }

      类型名通常是一个名词,且首字母大写!

2). 调用构造函数反复创建多个同一类型的对象

    var obj=new 构造函数(实参值列表);

3). 结果: 在内存中创建了一个包含规定属性结构的对象,且属性值为调用构造函数时传入的实参值。

4). 原理: new的原理: 4件事:

i. 创建一个新的空对象

ii. 自动设置当前子对象继承构造函数的原型对象(自动设置子对象的_ _proto_ _属性指向构造函数的原型对象)

iii. 用新对象调用构造函数,将构造函数中的this统一换成新对象。通过给新对象强行赋值新属性的方式,为新对象添加规定好的新属性。

iv. 返回新对象的地址给变量

5. 如何访问对象的成员:

(1). 如果访问对象的属性: 对象.属性名

(2). 如果调用对象中的方法: 对象.方法名()

二. 继承:

1. 什么是: 父对象的成员,子对象可以无需创建,就能直接使用!

2. 为什么: 代码重用!节约内存

构造函数只能代码重用!但是却浪费内存!构造函数会为每个子对象都重复创建多个方法的副本!

所以,将来构造函数中就不应该有方法定义!应该只包含属性定义就够了。

3. 何时: 今后,只要发现多个子对象都需要相同的成员时(方法定义或属性值),都要用继承来实现

4. 如何: js中的继承都是通过原型对象来实现的

(1). 什么是原型对象: 专门集中保存该类型下所有子对象共有成员的父对象。

(2). 何时: 只要使用继承,就要使用原型对象

(3). 如何:

a. 创建: 不用自己创建,买一赠一:

只要创建一个构造函数,都会附赠一个空的原型对象

b. 何时继承: new的第2步让新创建的子对象继承构造函数的原型对象: 自动设置新子对象的_ _proto_ _属性指向构造函数的原型对象

      只有从_ _proto_ _属性指出的关系,才叫继承关系!


c. 结果: 子对象将来,不需要重复创建,就可以使用保存在原型对象中的成员(方法或属性值)

  以上三步都不用自己写!已经在内存中定义好了,咱们直接使用!

    d. 如何向原型对象中添加新属性:只能强行赋值!——唯一需要自己做的一步操作

构造函数.prototype.方法名=function(){ ... }

5. 自有属性和共有属性:

(1). 自有属性: 直接保存在当前子对象中,仅归当前子对象独有的属性。

      只要构造函数妈妈肚子里的属性,都会成为孩子的自有属性。妈妈肚子里有什么,孩子将来自己就有什么!

(2). 共有属性: 保存在父对象中,归多个子对象共有的属性

(3). 获取属性值: 都可用"对象.属性名"

(4). 修改属性值: 自有属性可用子对象自己修改,而共有属性不能用子对象修改,只能用原型对象修改。

      如果强行用子对象修改共有属性,会为这个子对象自动添加一个同名的自有属性,从此这个子对象和大家在这个属性的使用上分道扬镳。

6. 内置对象的原型对象:

(1). ES标准中规定的11种内置对象:

String, Number, Boolean —— 包装类型

        Array  Date  Math  RegExp

Error

Function  Object

global (在浏览器中被window代替)

(2). 除了Math和global外,其余都是一种类型:

a. 每种类型中都有构造函数,专门负责创建这个类型的子对象。所以才能new!比如: new Array(), new Date(), new RegExp()

b. 每种类型中都有原型对象,专门保存这个类型的所有子对象共有的函数!所以,将来看这个类型都有哪些函数可用,就看这个类型的原型对象。比如: 所有数组都可以.sort(),都可以.push(),是因为数组类型的原型对象中包含这些函数,所以孩子们才能直接使用!

(3). 为内置类型如何扩展新函数:

其实就是定义一个函数保存在这个类型的原型对象中

比如: 为数组类型添加一个求和的方法sum,让所有数组家孩子都可以对内容求和:

Array.prototype.sum=function(){

var total=0;

for(var i=0;i

total+=this[i]

}

return total;

}

调用时:

[1,2,3].sum() //6

        强调: 原型对象中的方法中的this,指向将来调用这个方法的点前的某一个具体子对象。与定义在哪儿无关。

(4). 包装类型:

a. 什么是包装类型: 专门封装一个原始类型的值,并提供对原始类型的值执行操作的函数 的对象

b. 为什么: 因为原始类型的值只是一个值而已,没有任何自带的功能(函数/属性)。

c. 何时: 不用自己用,都是自动使用。只要试图对原始类型的值调用函数或访问属性时,都会自动创建该类型的包装类型对象。我们访问的函数或属性,其实都是包装类型对象提供的。

比如: "hello".toUpperCase()

等效于自动new String("hello").toUpperCase()

d. 包装类型对象在调用完函数后,自动释放,不会保留下来。

e. 为包装类型添加新的自定义函数:

1). 包装类型也包含两部分: 构造函数和原型对象

2). 只要将新的自定义方法自动添加到包装类型的原型对象中,就可以让所有该类型的值使用该方法。

创建对象: 3种方式:

1. 只能创建一个对象,且已知对象的内容

  var obj={ 属性名:属性值, 方法名:function(){ ... } }

2. 只创建一个对象,但是暂时不知道对象的内容

  var obj={}

  obj.属性名=属性值;

  obj.方法名=function(){ ... }

3. 反复创建多个相同结构的对象时:

  function 类型名(属性形参){

    this.属性名=形参;

    this.方法名=function(){

      ... this.属性名 ...

    }

  }

  var obj=new 类型名(属性值列表)

总结: 继承: 只要所有子对象共有的方法或属性值都要强行赋值到构造函数的原型对象中保存。

  构造函数.prototype.方法名=function(){ ... }

  构造函数.prototype.属性名=值

总结: this

1. obj.fun()  this-> obj

2. fun()  this->window

3. new Fun()  this->new新对象

作业:

1. this的笔试题

=== DAY05 ============================================================================

正课:

1. 面向对象

2. ES5

一. 面向对象

1. 继承:

(1). 原型链:

a. 什么是: 多级父对象逐级继承形成的链式结构

b. 保存着一个对象可用的所有成员。

1). 只要这个对象的原型链中包含的成员,不管是定义在哪一级父对象中,该都可使用。

2). 反之,如果不包含在原型链中的成员,该对象无法使用!

c. 控制着成员的使用顺序: 先用自有的,如果自己没有,才用共有的成员

2. 多态:

(1). 什么是多态: 一个函数在不同情况下表现出不同的状态

(2). 包括2种情况:

a. 重载:

b. 重写(override): 如果子对象觉得从父对象继承来的成员不好用,可在子对象内部定义和父对象成员同名的自有成员。结果,从此子对象只用自己的自有成员,而不再使用父对象继承来的同名成员。

1). 为什么: 不是所有从父对象继承来的成员都是好用的!

2). 何时: 如果子对象觉得从父对象继承来的成员不好用,就可以重写

3). 如何: 在子对象内部定义和父对象成员同名的自有成员.

4). 结果: 从此子对象只用自己的自有成员,而不再使用父对象继承来的同名成员。

3. 自定义继承关系: 如果觉得当前父对象不好用,可以换掉!

(1). 只更换一个对象的父对象: 只要修改_ _proto_ _指向新的父对象就行了

child._ _proto_ _=father

Object.setPrototypeOf( child  , father)  推荐

    修改 (原型<-的<-child) 为 father

(2). 修改所有子对象的爹: 其实就是修改构造函数的prototype属性指向新的原型对象

时机: 在创建子对象之前,就要换!

构造函数.prototype=新原型对象

(3). 两种类型间的继承: 看视频!

总结: 面向对象三大特点,也是三个步骤:

1. 封装: 创建对象3种方式:

(1). var obj={ 属性名: 值, 方法名:function(){ ... } }

(2). var obj={}  obj.属性名=值; obj.方法名=function(){ ... }

(3). function 类型名(属性参数列表){

      this.属性名=属性参数

  }

  var obj=new 类型名(属性值列表)

2. 继承: 今后所有的方法都应该定义在原型对象中,所有子对象共有

类型名.prototype.方法名=function(){ ... }

3. 多态: 如果从爹继承来的成员不好用,可以自己重写同名成员

4. 自定义继承: 如果觉得当前的爹不满意可以修改原型对象:

(1). 只修改一个对象的原型对象:

    Object.setPrototypeOf(child, father)

(2). 修改当前类型下所有子对象的原型对象:

    构造函数.prototype=新原型对象

二. ES5: ECMAScript标准的第5个版本

1. 严格模式:

(1). 什么是: 比普通js运行机制要求更严格的模式

(2). 为什么: js语言本身拥有很多广受诟病的缺陷

(3). 何时: 今后所有项目都要在严格模式下运行!

(4). 如何启用严格模式: 只要在当前代码段顶部添加"use strict";

结果: 整段代码都会以严格模式运行

(5). 新规定:

a. 禁止给未声明的变量赋值:

1). 旧js: 一个未声明的变量,可随时强行赋值,结果自动在全局创建该变量——全局污染,也会有歧义

2). 严格模式: 禁止给未声明的变量强行赋值,报错: xxx is not defined。——防止全局污染,也减少了拼写错误导致的误会

b. 静默失败升级为错误:

1). 什么是静默失败: 执行不成功,也不报错——极其不便于调试程序。

2). 严格模式: 所有静默失败都升级为错误!都会报错——便于快速发现问题,调试程序!

c. 普通函数调用中的this不再指向window,而是undefined

1). 旧js中: 普通函数和匿名函数自调中的中的this,默认指向window。——全局污染

2). 严格模式: 普通函数调用中的this不再指向window,而是undefined——避免了全局污染

d. 禁用了arguments.callee

1). arguments.callee是专门在当前函数内,引用当前函数自己的关键词

2). 何时: 只要实现递归调用函数时都用arguments.callee

3). 为什么: 如果在函数内递归调用自己时,写死函数名,会形成紧耦合。

4). this vs arguments.callee

i. this指的是正在调用当前函数的.前的对象,而不是当前函数本身。

        ii. arguments.callee指的是正在执行的函数本身,而不关心有没有点,或者点前是谁!

5). 禁用arguments.callee不是因为arguments. callee不好,而是因为递归算法不好。因为重复计算量太大!效率极低!禁用arguments.callee就是在暗示尽量少用递归

6). 解决: 今后尽量用循环代替递归算法

2. 保护对象:

(1). 什么是: 阻止对对象的属性值以及对象的结构进行不合法的修改。

(2). 为什么: js中的对象本身很弱,毫无自保能力

(3). 何时: 只要希望阻止用户对对象的属性值和结构进行不合法的修改时

(4). 如何:

a. 保护对象的属性:

1). ES5将对象的属性又进行了细致的分类

i. 命名属性: 可用.随时访问到的属性——需要保护

又被细分为2小类:

数据属性: 实际存储属性值的属性

访问器属性: 不实际存储属性值,只是对另外一个数据属性提供保护!

ii. 内部属性: 无法用.访问到的,但是对象内实际却存在的属性。比如: class属性。——不用保护

2). 如何保护数据属性:

i. 其实每个数据属性,都是一个缩微的小对象

ii. 每个数据属性的缩微对象中都包含四个属性:

{

value: 属性值, //实际存储属性值

writable: true/false, //控制是否可修改当前属性的值

enumerable: true/false, //控制是否可被for in遍历到该属性的值——半隐藏

configurable: true/false

//控制是否可删除该属性

//控制是否可修改前两个开关

}

iii. 开关属性一定不能用.去访问!只能用专门的函数去访问:

Object.defineProperty(对象, "属性名", {

  开关名: true/false

... : ...

        })

iv. 问题: defineProperty一次只能修改一个属性中的开关,如果修改多个属性中的开关,要重复写很多遍,很繁琐!

v. 解决:

Object.defineProperties(对象, {

属性名: {

开关: true/false,

... : ...

},

属性名: { ... }

})

vi: 问题: 开关的保护是死板的,不灵活的。无法用自定义规则保护属性

3). 访问器属性:

i. 什么是: 不实际存储属性值,只是对另外一个数据属性提供保护 的特殊属性

ii. 何时: 只要使用自定义规则保护属性时,就必须用访问器属性。

iii. 为什么: 仅靠开关无法用自定义规则保护属性。

iv. 如何: 2步:

首先将要保护的数据属性隐姓埋名,还要半隐藏:

比如: 本意是保护年龄属性eage:

Object.defineProperties({

_eage:{

value:属性值,

writable: true,

enumerable:false,

configurable:false

    }

})

然后添加访问器属性来保护这个要保护的数据属性:

Object.defineProperties(对象,{

_eage:{ ... },

eage:{

    get:function(){

      return this._eage;

    },

    set:function(value){

      if(value>=18&&value<=65){

        this._eage=value;

      }else{

        throw Error("年龄超范围!")

      }

    },

    enumerable:true,

    configurable:false

  }

})

          强调: 访问器属性eage中,不再有value和writable属性。因为,访问器属性不实际存储属性值,所以不需要value属性。又因为正是因为writable开关太死板,无法用自定义属性保护属性,所以才用访问器属性代替的writable开关。所以,也不需要writable属性了。

v. 如何使用访问器属性: 访问器属性在用法上和普通属性是完全一样的:

获取属性值: 对象.eage

原理: 会自动调用eage中的get(),自动从半隐藏的this._eage属性中获得属性值返回。

修改属性值: 对象.eage=新值

原理: 会自动调用eage中的set(),自动将=后的新值传递给参数value,在set()中先验证value是否符合要求,如果符合要求,才赋值给this._eage。否则报错,且不赋值

b. 保护对象的结构: 3个级别:

1). 防扩展: 阻止对这个对象添加新属性:

i. Object.preventExtensions(obj)

    阻止 扩展

ii. 结果: 如果再给obj对象添加新属性,会报错!

iii. 原理: 每个对象中都隐藏着一个内部属性: extensible: true。所有对象默认都可以扩展。preventExtensions()自动将内部属性extensible:false,禁止扩展。

2). 密封: 在兼具防扩展同时,又进一步禁止删除所有属性

i. 如何: Object.seal(obj)

ii. 原理: 1. 自动调用preventExtensions()

                2. 自动修改所有属性的configurable:false。从此每个属性上不用再加configurable:false了。

iii. 密封只是禁止添加或删除属性,但是属性值还是可以随意修改的。

iv. 大部分对象只要保护到密封级别就够了

3). 冻结: 兼具密封的基础上,进一步禁止修改所有属性值

i. 如何: Object.freeze(obj)

ii. 原理: 1. 自动调用preventExtensions()

2. 自动修改所有属性的configurable和writable都为false

iii. 何时: 多个模块共用的对象内容,不允许随便一个模块擅自修改。

作业:

1. 判断一个对象是不是数组类型,共有几种方式?

2. 实现两种类型间的继承

你可能感兴趣的:(2021-04-09)