4.JavaScript Class 封装和继承

4.JavaScript Class 封装和继承

了解函数中 this 在不同场景下的默认值,动态指定函数 this 的值,提升代码封装的灵活度。

  • 能够区分不同场景下函数中 this 的默认值
  • 知道箭头函数的普通函数的区别,掌握箭头函数的使用
  • 能够动指定函数中 this 的值
  • 了解基于类的面向对象的实现语法

一、this

了解函数中 this 在不同场景下的默认值,知道动态指定函数 this 值的方法。

1.1 默认值

this 是 JavaScript 最具“魅惑”的知识点,不同的应用场合 this 的取值可能会有意想不到的结果,在此我们对以往学习过的关于【 this 默认的取值】情况进行归纳和总结。

普通函数

普通函数的调用方式决定了 this 的值,即【谁调用 this 的值指向谁】,如下代码所示:

<script>
  // 普通函数
  function sayHi() {
    console.log(this);
  }
  // 函数表达式
  let sayHello = function () {
    console.log(this);
  }
	
  // 函数的调用方式决定了 this 的值
  sayHi(); // window
  window.sayHi();
	
 	// 普通对象
  let user = {
    name: '小明',
    walk: function () {
      console.log(this);
    }
  };
  // 动态为 user 添加方法
  user.sayHi = sayHi;
  uesr.sayHello = sayHello;
	
  // 函数调用方式,决定了 this 的值
  user.sayHi();
  user.sayHello();
</script>

注: 普通函数没有明确调用者时 this 值为 window,严格模式下没有调用者时 this 的值为 undefined

箭头函数

箭头函数中的 this 与普通函数完全不同,也不受调用方式的影响,事实上箭头函数中并不存在 this !箭头函数中访问的 this 不过是箭头函数所在作用域的 this 变量。

<script>
  console.log(this); // 此处为 window
  // 箭头函数
  let sayHi = function() {
    console.log(this); // 该箭头函数中的 this 为函数声明环境中 this 一致
  }

  // 普通对象
  let user = {
    name: '小明',
    // 该箭头函数中的 this 为函数声明环境中 this 一致
    walk: () => {
      console.log(this);
    },
    
    sleep: function () {
      let str = 'hello';
      console.log(this);
      let fn = () => {
        console.log(str);
        console.log(this); // 该箭头函数中的 this 与 sleep 中的 this 一致
      }
      // 调用箭头函数
      fn();
    }
  }

  // 动态添加方法
  user.sayHi = sayHi;
  
  // 函数调用
  user.sayHi();
  user.sleep();
  user.walk();
</script>

在开发中【使用箭头函数前需要考虑函数中 this 的值】,事件回调函数使用箭头函数时,this 为全局的 window,因此DOM事件回调函数不推荐使用箭头函数,如下代码所示:

<script>
  // DOM 节点
  let btn = document.querySelector('.btn');

  // 箭头函数 此时 this 指向了 window
  btn.addEventListener('click', () => {
    console.log(this);
  })

  // 普通函数 此时 this 指向了 DOM 对象
  btn.addEventListener('click', function () {
    console.log(this);
  })
</script>

同样由于箭头函数 this 的原因,基于原型的面向对象也不推荐采用箭头函数,如下代码所示:

<script>
  function Person() {

  }

  // 原型对像上添加了箭头函数
  Person.prototype.walk = () => {
    console.log('人都要走路...');
    console.log(this); // widow
  }

  let p1 = new Person();
  p1.walk();
</script>
1.2 定义值

以上归纳了普通函数和箭头函数中关于 this 默认值的情形,不仅如此 JavaScript 中还允许指定函数中 this 的指向,有 3 个方法可以动态指定普通函数中 this 的指向:

call

使用 call 方法调用函数,同时指定函数中 this 的值,使用方法如下代码所示:

<script>
  // 普通函数
  function sayHi() {
    console.log(this);
  }

  let user = {
    name: '小明',
    age: 18
  }

  let student = {
    name: '小红',
    age: 16}

  // 调用函数并指定 this 的值
  sayHi.call(user); // this 值为 user
  sayHi.call(student); // this 值为 student

  // 求和函数
  function counter(x, y) {
    return x + y;
  }

  // 调用 counter 函数,并传入参数
  let result = counter.call(null, 5, 10);
  console.log(result);
</script>

总结:

  1. call 方法能够在调用函数的同时指定 this 的值
  2. 使用 call 方法调用函数时,第1个参数为 this 指定的值
  3. call 方法的其余参数会依次自动传入函数做为函数的参数
apply

使用 call 方法调用函数,同时指定函数中 this 的值,使用方法如下代码所示:

<script>
  // 普通函数
  function sayHi() {
    console.log(this);
  }

  let user = {
    name: '小明',
    age: 18
  }

  let student = {
    name: '小红',
    age: 16
  }

  // 调用函数并指定 this 的值
  sayHi.apply(user); // this 值为 user
  sayHi.apply(student); // this 值为 student

  // 求和函数
  function counter(x, y) {
    return x + y;
  }

  // 调用 counter 函数,并传入参数
  let result = counter.apply(null, [5, 10]);
  console.log(result);
</script>

总结:

  1. apply 方法能够在调用函数的同时指定 this 的值
  2. 使用 apply 方法调用函数时,第1个参数为 this 指定的值
  3. apply 方法第2个参数为数组,数组的单元值依次自动传入函数做为函数的参数
bind

bind 方法并不会调用函数,而是创建一个指定了 this 值的新函数,使用方法如下代码所示:

<script>
  // 普通函数
  function sayHi() {
    console.log(this);
  }

  let user = {
    name: '小明',
    age: 18
  }

  // 调用 bind 指定 this 的值
  let sayHello = sayHi.bind(user);

  // 调用使用 bind 创建的新函数
  sayHello();
</script>

注:bind 方法创建新的函数,与原函数的唯一的变化是改变了 this 的值。

改变this三个方法总结:

call:fun.call(this,arg1, arg2,......)
apply:fun.apply(this, [arg1, arg2,......])
bind:fun.bind(this, arg1, arg2,......)

相同点:
	都可以用来改变this指向,第一个参数都是this指向的对象
区别:
	call和apply:都会使函数执行,但是参数不同
	bind:不会使函数执行,参数同call

二、class

了解 JavaScript 中基于 class 语法的面向对象编程,为后续课程中的应用做好铺垫。

传统面向对象的编程序语言都是【类】的概念,对象都是由类创建出来,然而早期 JavaScript 中是没有类的,面向对象大多都是基于构造函数和原型实现的,但是 ECMAScript 6 规范开始增加了【类】相关的语法,使得 JavaScript 中的面向对象实现方式更加标准。

2.1 封装

class(类)是 ECMAScript 6 中新增的关键字,专门用于创建类的,类可被用于实现逻辑的封装。

<script>
  // 创建类
  class Person {
	// 此处编写封装逻辑
  }

  // 实例化
  let p1 = new Person();
  console.log(p1);
</script>
实例成员
<script>
  // 创建类
  class Person {
    // 实例属性
    name = '小明';

    // 实例方法
    sleep () {
      console.log('sleeping...')
    }
  }

  // 实例化
  let p1 = new Person();
  p1.sayHi();
</script>

总结:

  • 关键字 class 封装了所有的实例属性和方法

  • 类中封装的并不是变量和函数,因此不能使用关键字 letconstvar

静态成员
<script>
  // 创建类
  class Person {
    // 静态属性
    static version = '1.0.0';

    // 静态方法
    static getVersion = function () {
      console.log(this.version);
    }
  }
	
  // 静态方法直接访问
  console.log(Person.version);
  Person.getVersion();
</script>

总结:

  • static 关键字用于声明静态属性和方法
  • 静态属性和方法直接通过类名进行访问
构造函数

创建类时在类的内部有一个特定的方法 constructor ,该方法会在类被实例化时自动被调用,常被用于处理一些初始化的操作。

<script>
  class Person {
    // 实例化时 立即执行
    // 构造函数、构造方法、构造器
    constructor (name, age) {
      this.name = name;
      this.age = age;
    }
	// 实例方法
    walk () {
      console.log(this.name + '正在走路...');
    }
  }
	
  // 实例化
  let p1 = new Person('小明', 18);
  p1.walk();
</script>

总结:

  • constructor 是类中固定的方法名
  • constructor 方法在实例化时立即执行
  • constructor 方法接收实例化时传入的参数
  • constructor 并非是类中必须要存在的方法
2.2 继承
extends

extends 是 ECMAScript 6 中实现继承的简洁语法,代码如下所示:

<script>
  class Person {
    // 父类的属性
    legs = 2;
    arms = 2;
    eyes = 2;
		// 父类的方法
    walk () {
      console.log('人类都会走路...');
    }
		// 父类的方法
    sleep () {
      console.log('人都得要睡觉...');
    }
  }

  // Chinese 继承了 Person 的所有特征
  class Chinese extends Person {}

  // 实例化
  let c1 = new Chinese();
  c1.walk();
</script>

如上代码所示 extends 是专门用于实现继承的语法关键字,Person 称为父类、Chinese 称为子类。

super

在继承的过程中子类中 constructor 中必须调 super 函数,否则会有语法错误,如下代码所示:

<script>
  class Person {
    // 构造函数
    constructor (name, age) {
      this.name = name;
      this.age = age;
    }
    
    // 父类的属性
    legs = 2;
  	arms = 2;
  
    walk () {
      console.log('人类都会走路...');
    }
  }

  // 子类 English 继承了父类 Person
  class English extends Person {
    // 子类的构造函数
    constructor (name, age) {
      super(name, age);
    }

    // 子类的属性
    skin = 'white';
  	language = '英文';
    }

  // 实例化
  let e1 = new English('jack', 18);
  console.log(e1.name);
</script>

子类构造函数中的 super 函数的作用是可以将子类实例化时获得的参数传入父类的构造函数之中。

2.3 写在最后

ECMAScript 6 中基于类的面向对象相较于构造函数和原型对象的面向对象本质上是一样的,基于类的语法更为简洁,未来的 JavaScript 中也都会是基于类的语法实现,当前阶段先熟悉基于类的语法,后面课程中会加强基于类语法的实践。

拷贝:

拷贝不是直接赋值

浅拷贝:

含义:只拷贝最外面层的拷贝方式
	let obj = {
			uname : '张三丰',
			age : 22,
			sex : '男',
			color : ['red', 'blue', 'yellow', 'pink'],
			message : {
				index : 1,
				score : 99
			}
		}

		let newObj = {};

		Object.assign(newObj, obj);

		console.log( obj, newObj );

深拷贝:

含义:所有层都拷贝的方式

let obj = {
			uname : '张三丰',
			age : 22,
			sex : '男',
			color : ['red', 'blue', 'yellow', 'pink'],
			message : {
				index : 1,
				score : 99
			}
		}

		let newObj = {};

		function kaobei (newObj, obj) {

			for ( let key in obj ) {

				if ( obj[key] instanceof Array ) {// obj[key] 是数组
					 // obj[key]是数组,遍历
					 newObj[key] = [];
					 kaobei(newObj[key], obj[key]);
				} else if ( obj[key] instanceof Object ) { // obj[key]是对象
					// obj[key]是对象,遍历
					newObj[key] = {};
					kaobei(newObj[key], obj[key]);
				} else {
					newObj[key] = obj[key];
				}

			}

		}

		kaobei(newObj, obj);

		obj.message.score = 123;

		console.log( obj, newObj );


  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
JavaScript电子书,包括了JavaScript的大部分知识,可以帮助读者快速入门。内容如下: 第1章 JavaScript语言概述 1.1 JavaScript是什么 1.1.1 JavaScript简史 1.1.2 JavaScript有何特点 1.2 JavaScript能做什么 1.2.1 表单数据合法性验证 1.2.2 页面特效 1.2.3 交互式菜单 1.2.4 动态页面 1.2.5 数值计算 1.3 JavaScript编程起步 1.3.1 “Hello World!”程序 1.3.2 选择JavaScript脚本编辑器 1.4 如何引入JavaScript脚本代码 1.4.1 通过与标记对引入 1.4.2 通过标记的src属性引入 1.4.3 通过JavaScript伪URL引入 1.4.4 通过HTML文档事件处理程序引入 1.5 嵌入JavaScript脚本代码的位置 1.5.1 在与标记对之间放置 1.5.2 在与标记对之间放置 1.5.3 在两个标记对之间混合放置 1.6 选择合适的浏览器 1.7 处理不支持JavaScript脚本的情况 1.7.1 使用标记对直接屏蔽法 1.7.2 使用和标记对给出提示信息 1.8 JavaScript的实现基础 1.8.1 ECMAScript 1.8.2 DOM 1.8.3 BOM 1.9 客户端与服务器端脚本 1.10 JavaScript与JScript、 VBScript 1.11 JavaScript与Java、Java applet 1.12 JavaScript的未来如何 1.13 本章小结 第2章 JavaScript语言入门 2.1 编程准备 2.1.1 编程术语 2.1.2 脚本执行顺序 2.1.3 大小写敏感 2.1.4 空白字符 2.1.5 分号 2.1.6 块 2.2 数值类型 2.2.1 整型和浮点数值 2.2.2 八进制和十六进制 2.3 变量 2.3.1 变量标识符 2.3.2 变量申明 2.3.3 变量作用域 2.4 弱类型 2.5 基本数据类型 2.5.1 Number型 2.5.2 String型 2.5.3 Boolean型 2.5.4 Undefined型 2.5.5 Null型 2.5.6 Function型 2.6 组合类型 2.6.1 Array型 2.6.2 Object型 2.7 运算符 2.7.1 赋值运算符 2.7.2 基本数学运算符 2.7.3 位运算符 2.7.4 位移运算符 2.7.5 自加和自减 2.7.6 比较运算符 2.7.7 逻辑运算符 2.7.8 逗号运算符 2.7.9 空运算符 2.7.10 ?...:运算符 2.7.11 对象运算符 2.7.12 typeof运算符 2.7.13 运算符优先级 2.8 核心语句 2.8.1 基本处理流程 2.8.2 if条件假设语句 2.8.3 switch流程控制语句 2.8.4 for循环语句 2.8.5 while和do-while循环语句 2.8.6 使用break和continue进行循坏控制 2.8.7 with对象操作语句 2.8.8 使用for…in进行对象循坏 2.8.9 含标签的语句 2.9 函数 2.9.1 函数的基本组成 2.9.2 全局函数与局部函数 2.9.3 作为对象的函数 2.9.4 函数递归调用 2.9.5 语言注释语句 2.9.6 函数应用注意事项 2.10 本章小结 第3章 JavaScript事件处理 3.1 什么是事件 3.2 HTML文档事件 3.2.1 事件捆绑 3.2.2 浏览器事件 3.2.3 HTML元素事件 3.2.4 IE扩展的事件 3.3 JavaScript如何处理事件 3.3.1 匿名函数 3.3.2 显式声明 3.3.3 手工触发 3.4 事件处理器的返回值 3.5 事件处理器设置的灵活性 3.6 现代事件模型与Event对象 3.7 IE4中的Event对象 3.7.1 对象属性 3.7.2 事件上溯 3.7.3 阻止事件上溯 3.7.4 事件改向 3.8 NN4中的Event对象 3.8.1 对象属性 3.8.2 事件捕获 3.8.3 关闭事件捕获 3.8.4 事件传递 3.9 DOM的解决之道

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值