前端架构师之08_JavaScript对象

1 面向对象概述

1.1 面向过程与面向对象

  • 面向过程:注重的是事情完成的具体的步骤,只有按照步骤一步一步的执行,才能够完成这件事情。
    • 对于面向过程思想,我们扮演的是执行者,凡事都要靠自己完成。
    • 面向过程思想的劣势,编写的代码都是一些变量和函数,随着程序功能的不断增加,变量和函数就会越来越多,此时容易遇到命名冲突的问题,由于各种功能的代码交织在一起,导致代码结构混乱,变得难以理解、维护和复用。
  • 面向对象:注重的是一个个对象,这些对象各司其职,我们只要发号施令,即可指挥这些对象帮我们完成任务。
    • 对于面向对象思想,我们扮演的是指挥官,只要找到相应的对象,让它们帮我们做具体的事情即可。
    • 面向对象思想的优势,可以将同一类事物的操作代码封装成对象,将用到的变量和函数作为对象的属性和方法,然后通过对象去调用,这样可以使代码结构清晰、层次分明。

1.2 面向对象的特征

封装性
  • 指的是隐藏内部的实现细节,只对外开放操作接口。
  • 使用:就是对象的方法,无论对象的内部多么复杂,用户只需知道这些接口怎么使用即可。
  • 优势:无论一个对象内部的代码经过了多少次修改,只要不改变接口,就不会影响到使用这个对象时编写的代码。
  • 电脑是非常高精密的电子设备,其实现原理也非常复杂,而用户在使用时并不需要知道这些细节,只要操作键盘和鼠标就可以使用。
继承性
  • 是指一个对象继承另一个对象的成员,从而在不改变另一个对象的前提下进行扩展。

  • String 对象就是对所有字符串的抽象,所有字符串都具有 toUpperCase() 方法,用来将字符串转换为大写,这个方法其实就是继承自 String 对象。

  • 优势:可在保持接口兼容的前提下对功能进行扩展。增强了代码的复用性,为程序的修改和补充提供便利。

多态性
  • 指的是同一个操作作用于不同的对象,会产生不同的执行结果。

  • 实际上 JavaScript 被设计成一种弱类型语言(即一个变量可以存储任意类型的数据),就是多态性的体现。

  • 数字、数组、函数都具有 toString() 方法,当使用不同的对象调用该方法时,执行结果不同。

    var obj = 123;
    console.log(obj.toString());	// 输出结果:123
    obj = [1, 2, 3];
    console.log(obj.toString());	// 输出结果:1,2,3
    obj = function() {};
    console.log(obj.toString());	// 输出结果:function () {}
    
  • 在面向对象中,多态性的实现往往离不开继承,这是因为当多个对象继承了同一个对象后,就获得了相同的方法,然后根据每个对象的不同来改变同名方法的执行结果。

虽然面向对象有封装、继承、多态这些设计思想,但并不表示只要满足这些特征就可以设计出优秀的程序,开发人员还需要考虑如何合理的运用这些特征。

  • 在封装时,如何给外部调用者提供完整且最小的接口,使外部调用者可以顺利得到想要的功能,不需要研究其内部的细节。
  • 在进行继承和多态设计时,对于继承了同一个对象的多种不同的子对象,如何设计一套相同的方法进行操作。

2 什么是对象

2.1 现实生活中的对象

万物皆对象,对象是一个具体的事物,是一种看得见、摸得着的东西。一个具体的事物就会有行为和特征。比如,一部车,一个手机,一个人都是一个对象。

手机的属性:颜色、重量、屏幕尺寸。

手机的方法:打电话、发短信、看视频、听音乐。

2.2 JavaScript 中的对象

JavaScript 中的对象其实就是生活中具体事物的一个抽象。

JavaScript 的对象是无序属性的集合,其属性可以包含基本值、对象或函数。对象就是一组没有顺序的值。我们可以把 JavaScript 中的对象想象成键值对,其中值可以是数据和函数。

对象的行为和特征

  • 特征—属性
    • 事物的特征在对象中用属性来表示。
  • 行为—方法
    • 事物的行为在对象中用方法来表示。

注:Javascript 是一种基于对象(object-based)的语言,在 JavaScript 中,任何不是数值、字符串、布尔值、符号、null和undefined的值都是对象。

3 对象分类

对象属于复合数据类型,有三种,分别是:

  • 内建对象
    • 由ES标准中内建的对象,在任何的ES实现中都可以使用,如Math, String, Boolean, Number, Function, Object…
  • 宿主对象
    • 由 js 运行的环境所提供的对象,一般指浏览器,如DOM, BOM…
  • 自定义对象
    • 由开发人员自定义的对象

4 自定义对象

4.1 对象的定义

语法:对象的定义是通过“{ }”语法实现的。

组成:对象以对象成员(属性和方法)构成,多个成员之间使用逗号分隔。

成员:对象的成员以键值对的形式存放在{}中。

var o1 = {};
var o2 = {name: 'Jim'};
var o3 = {name: 'Jim', age: 19, gender: '男'};
var o4 = {
    name: 'Jim',
    age: 19,
    gender: '男',
    sayHello: function() {
        console.log('你好');
    }
};

“{ }” 语法又称为对象的字面量语法,所谓字面量是指在源代码中直接书写的一个表示数据和类型的量,如123(数值型)、‘123’(字符型)、[123](数组)都是字面量。

JSON 数据格式

JSON: JavaScript Object Notation,JavaScript 对象符号。

用途:应用于数据存储和交互。

语法: JSON 是一个字符串,使用双引号包裹对象的成员名和字符串型的值。

JSON 与对象的区别:

  • JSON 是一个字符串。
  • JSON 不仅可以用来保存对象,还可以保存数字、字符串、数组等其他类型的数据。
{"name":"Tom","age":24,"work":true,"arr":[1,2]}
// 或
[{"name":"Tom","age":24},{"name":"Jim","age":25}]

4.2 访问对象成员

语法:对象.成员。

var o5 = {};                           // 创建一个空对象
o5.name = 'Jack';                      // 为对象增加属性
o5.introduce = function () {           // 为对象增加方法
    alert('My name is ' + this.name);  // 在方法中使用this代表当前对象
};
alert(o5.name);                        // 访问name属性,输出结果:Jack
o5.introduce();                        // 调用introduce()方法,输出结果:My name is Jack

可变成员名语法:对象[变量名] = 值。

var o6 = {};		// 创建一个空对象
var key = 'id';		// 通过变量保存要操作的属性名
o6[key] = 123;		// 相当于“o6['id'] = 123”或“o6.id = 123”

4.3 对象成员遍历

语法:for…in。

var obj = {name: 'Tom', age: 16};
for (var k in obj) {
    console.log(k + '-' + obj[k]);
}
  • 变量k保存了每个对象成员的名称。
  • obj[k] 访问成员属性的值。
  • obj[k]() 调用成员方法。
判断对象成员是否存在
  • 当需要判断一个对象中的某个成员是否存在时,可以使用 in 运算符。
  • 当对象的成员存在时返回true,不存在时返回false。
var obj = {name: 'Tom', age: 16};
console.log('name' in obj);    // 输出结果:true
console.log('gender' in obj);  // 输出结果:false

4.4 深拷贝与浅拷贝

拷贝(copy):是指将一个目标数据复制一份,形成两个个体。

深拷贝:参与拷贝的两个目标,改变其中一个目标的值,不会影响另一个目标的值。

浅拷贝:参与拷贝的两个目标,一个目标的值改变,另一个目标的值也会随之改变。

  • 浅拷贝只是拷贝一层,更深层次对象级别的只拷贝引用
  • 深拷贝拷贝多层,每一级别的数据都会拷贝
  • Object.assign(target,…sources) ES6新增方法可以浅拷贝

如何区分深拷贝与浅拷贝,简单点来说,就是假设B复制了A,当修改A时,看B是否会发生变化,如果B也跟着变了,说明这是浅拷贝,拿人手短,如果B没变,那就是深拷贝,自食其力。

实现深拷贝
  • 基本类型(如数值、字符型):通过变量赋值即可实现。
  • 引用类型(如数组、对象):复制对象里的成员到另一个对象。
// 深拷贝拷贝多层,每一级别的数据都会拷贝
var obj = {
    id: 1,
    name: 'andy',
    msg: {
        age: 18
    }
    color: ['pink','red']
};
var o = {};
// 封装函数
function deepCopy(newobj,oldobj){
    for(var k in oldobj){
        // 判断属性值属于简单数据类型还是复杂数据类型
        // 1.获取属性值   oldobj[k]
        var item = oldobj[k];
        // 2.判断这个值是否是数组
        if(item instanceof Array){
            newobj[k] = [];
            deepCopy(newobj[k],item)
        }else if (item instanceof Object){
            // 3.判断这个值是否是对象
            newobj[k] = {};
            deepCopy(newobj[k],item)
        }else {
            // 4.属于简单数据类型
            newobj[k] = item;
        } 
    }
}
deepCopy(o,obj);

注:JSON.parse( JSON.stringify(obj)); 也可以实现深拷贝,需要注意这种方法实现的深拷贝无法拷贝 function、undefined、symbol

实现浅拷贝
  • 引用类型(如数组、对象):通过变量赋值即可实现。
  • 提示:浅拷贝是引用类型中才有的概念。
// 浅拷贝只是拷贝一层,更深层次对象级别的只拷贝引用
var obj = {
    id: 1,
    name: 'andy',
    msg: {
        age: 18
    }
};
var o = {}
for(var k in obj){
    // k是属性名,obj[k]是属性值
    o[k] = obj.[k];
}
console.log(o);
// 浅拷贝语法糖
Object.assign(o,obj);
浅拷贝的优势

浅拷贝可以节省内存开销。

5 构造函数

5.1 为什么使用构造函数

构造函数是 JavaScript 创建对象的另外一种方式。

与字面量方式创建对象对比:构造函数可以创建出一些具有相同特征的对象。

比如通过水果构造函数创建苹果、香蕉、橘子对象。其特点在于这些对象都基于同一个模板创建,同时每个对象又有自己的特征。

字面量的方式创建对象的特点

  • 优势:简单灵活。
  • 劣势:当需要创建一组具有相同特征的对象时,无法通过代码指定这些对象应该具有哪些相同的成员。

面向对象编程语言的实现模板的方式:利用类(class)创建模板,根据模板实现不同的对象(类的实例)。

  • JavaScript 实现模板的方式1:通过工厂函数,在其内部通过字面量“{ }”的方式创建对象来实现,缺点是无法区分对象的类型。
  • JavaScript 实现模板的方式2:通过构造函数创建对象。

5.2 JavaScript 内置的构造函数

JavaScript内置的构造函数

  • 常见的内置构造函数:Object、String、Number等构造函数。

构造函数如何创建对象:new 构造函数名()。

将使用 new 关键字创建对象的过程称为实例化,实例化后得到的对象称为构造函数的实例。

// 通过构造函数创建对象
var obj = new Object();         // 创建 Object 对象
var str = new String('123');    // 创建 String 对象
// 查看对象是由哪个构造函数创建的
console.log(obj.constructor);   // 输出结果:function Object() { [native code] }
console.log(str.constructor);   // 输出结果:function String() { [native code] }
console.log({}.constructor);    // 输出结果:function Object() { [native code] }
  • “对象.constructor”属性指向了该对象的构造函数。
  • 通过 console.log() 输出时,[native code] 表示该函数的代码是内置的

5.3 自定义构造函数

  • 构造函数的命名推荐采用帕斯卡命名规则,即所有的单词首字母大写。
  • 在构造函数内部,使 this 来表示刚刚创建的对象。
// 自定义构造函数
function Person(name, age) {
    this.name = name;
    this.age = age;
    this.sayHello = function () {
        console.log('Hello, my name is ' + this.name);
    };
}

// 使用构造函数
var p1 = new Person('Jack', 18);
var p2 = new Person('Alice', 19);
console.log(p1);		
console.log(p2);		
p1.sayHello();
console.log(p1.constructor);

注意:

  • JavaScript 中,大家经常会对一些相近的名词感到困惑,如函数、方法、构造函数、构造方法、构造器等。
  • 实际上,它们都可以统称为函数,只不过在不同使用场景下的称呼不同。根据习惯,对象中定义的函数称为对象的方法。
  • 对于构造函数,也有一部分人习惯将其称为构造方法或构造器,我们只需明白这些称呼所指的是同一个事物即可。
ES6 新增的 class 关键字
  • ES6 前没有的原因:为了简化难度。
  • 新增原因:随着 Web 前端技术发展,一部分原本从事后端开发的人员转向了前端。为了让 JavaScript 更接近一些后端语言的语法从而使开发人员更快地适应。
  • class关键字的作用:用来定义一个类。
  • 特点:在类中可以定义 constructor 构造方法。

注意:class 语法本质上是语法糖,只是方便用户使用而设计的,不使用该语法同样可以达到相同的效果,如前面学过的构造函数。为了避免用户的浏览器不支持此语法,因此不推荐使用此方式。

5.4 私有成员

在构造函数中,使用 var 关键字定义的变量称为私有成员。

特点:在实例对象后无法通过“对象.成员”的方式进行访问,但是私有成员可以在对象的成员方法中访问。

特性:私有成员 name 体现了面向对象的封装性。

function Person() {
    var name = 'Jim';
    this.getName = function () {
        return name;
    };
}
var p = new Person();    // 创建实例对象p
console.log(p.name);     // 访问私有成员,输出结果:undefined
p.getName();             // 访问对外开放的成员,输出结果:Jim
构造函数中的 return 关键字

构造函数的本质是函数,因此构造函数中也可以使用 return 关键字。

构造函数在使用时与普通函数有一定的区别:

  • 若用 return 返回一个数组或对象等引用类型数据,则构造函数直接返回该数据,而不会返回原来创建的对象。
  • 若返回的是基本类型数据,则返回的数据无效,依然会返回原来创建的对象。
// 返回基本类型数据
function Person() {
    obj = this;
    return 123;
}
var obj, p = new Person();
console.log(p === obj);   // true

// 返回引用类型数据
function Person() {
    obj = this;
    return {};
}
var obj, p = new Person();
console.log(p === obj);   // false

5.5 函数中的 this 指向

this 的特点:根据函数不同的调用方式,函数中的 this 指向会发生改变。

this 指向

在 JavaScript 中,函数内的 this 指向通常与以下 3 种情况有关。

  • new 关键字将函数作为构造函数调用时,构造函数内部的 this 指向新创建的对象。
  • 直接通过函数名调用函数时,this 指向全局对象(浏览器中表示 window 对象)。
  • 如果将函数作为对象的方法调用,this 将会指向该对象。
function foo() {
    return this;
}
var o = {name: 'Jim', func: foo};
console.log(foo() === window);    // 输出结果:true
console.log(o.func() === o);      // 输出结果:true
更改 this 指向

除了遵循默认的 this 指向规则,函数的调用者还可以利用 JavaScript 提供的两种方式手动控制 this 的指向。

  • apply() 方法
  • call() 方法
function method() {
    console.log(this.name);
}
// 输出结果:张三
method.apply({name: '张三'}); 
// 输出结果:李四
method.call({name: '李四'});


function method(a, b) {
    console.log(a + b);
}
// 数组方式传参,输出结果:12
method.apply({}, ['1', '2']); 
// 参数方式传参,输出结果:34
method.call({}, '3', '4');

总结:

  • 相同点:apply() 和 call() 方法的第1个参数表示将 this 指向哪个对象。
  • 不同点: apply() 方法的第2个参数表示调用函数时传入的参数,通过数组的形式传递; call() 方法的第2~N个参数来表示调用函数时传入的函数。
ES5 新增的 bind() 方法
  • bind() 方法的作用:用于在调用函数前指定this的含义,实现提前绑定的效果。
  • bind() 方法的第1个参数:将 this 指向哪个对象。
  • bind() 方法的第2~N个参数:表示调用函数时传入的函数。
function method(a, b) {
    console.log(this.name + a + b);
}
var name = '张三';
var test = method.bind({name: '李四'}, '3', '4');
method('1', '2');    // 输出结果:张三12
test();              // 输出结果:李四34

6 内置对象

6.1 String对象

  • 字符型数据的创建:利用一对单引号或双引号。

  • 字符型数据为什么能像对象一样使用呢?这是因为这些对象实际上是构造函数 String 的实例,即 String 对象。

  • String 对象提供了一些用于对字符串进行处理的属性和方法。

成员作用
length获取字符串的长度
charAt(index)获取index位置的字符,位置从0开始计算
indexOf(searchValue)获取searchValue在字符串中首次出现的位置
lastIndexOf(searchValue)获取searchValue在字符串中最后出现的位置
substring(start[, end])截取从start位置到end位置之间的一个子字符串
substr(start[, length])截取从start位置开始到length长度的子字符串
toLowerCase()获取字符串的小写形式
toUpperCase()获取字符串的大写形式
split([separator[, limit])使用separator分隔符将字符串分隔成数组,limit用于限制数量
replace(str1, str2)使用str2替换字符串中的str1,返回替换结果

对字符串进行操作时,处理结果是通过方法的返回值直接返回的,并不会改变String对象本身保存的字符串内容。在这些方法的参数中,位置是一个索引值,从0开始计算,第一个字符的索引值是0,最后一个字符的索引值是字符串的长度减1。

// 限制用户名长度在3~10范围内,不允许出现敏感词admin
var name = 'Administrator';
if (name.length < 3 || name.length > 10) {
    alert('用户名长度必须在3~10之间。');
}
if (name.toLowerCase().indexOf('admin') !== -1) {
    alert('用户名中不能包含敏感词:admin。');
}

6.2 Number对象

Number 对象用于处理整数、浮点数等数值,常用的属性和方法如下。

成员作用
MAX_VALUE在JavaScript中所能表示的最大数值(静态成员)
MIN_VALUE在JavaScript中所能表示的最小正值(静态成员)
toFixed(digits)使用定点表示法来格式化一个数值
var num = 12345.6789;
num.toFixed();       // 四舍五入,不包括小数部分,返回结果:12346
num.toFixed(1);      // 四舍五入,保留1位小数,返回结果:12345.7
num.toFixed(6);      // 用0填充不足的小数位,返回结果:12345.678900
Number.MAX_VALUE;    // 获取最大值,返回结果:1.7976931348623157e+308
Number.MIN_VALUE;    // 获取最小正值,返回结果:5e-324

是 Number 的静态成员,直接通过构造函数 Number 进行访问,而非 Number 的实例。

6.3 Math对象

Math对象用于对数值进行数学运算,与其他对象不同的是,该对象不是一个构造函数,不需要实例化就能使用。

成员作用
PI获取圆周率,结果为3.141592653589793
abs(x)获取x的绝对值,可传入普通数值或是用字符串表示的数值
max([value1[,value2, …]])获取所有参数中的最大值
min([value1[,value2, …]])获取所有参数中的最小值
pow(base, exponent)获取基数(base)的指数(exponent)次幂,即 baseexponent
sqrt(x)获取x的平方根
ceil(x)获取大于或等于x的最小整数,即向上取整
floor(x)获取小于或等于x的最大整数,即向下取整
round(x)获取x的四舍五入后的整数值
random()获取大于或等于0.0且小于1.0的随机值

公式为 Math.random() * (n - m) + m,表示生成大于或等于 m 且小于 n 的随机值

Math.random() * (3 - 1) + 1;       //  1 ≤ 返回结果 < 3
Math.random() * (20 - 10) + 10;    // 20 ≤ 返回结果 < 20
Math.random() * (99 - 88) + 88;    // 88 ≤ 返回结果 < 99
Math.floor(Math.random() * (3- 1+ 1) + 1);    // 返回结果 是1、2、3中的任意一个

6.4 Date对象

Date对象用于处理日期和时间。

成员作用
getFullYear()获取表示年份的4位数字,如2020
setFullYear(value)设置年份
getMonth()获取月份,范围0~11(0表示一月,1表示二月,依次类推)
setMonth(value)设置月份
getDate()获取月份中的某一天,范围1~31
setDate(value)设置月份中的某一天
getDay()获取星期,范围0~6(0表示星期日,1表示星期一,依次类推)
getHours()获取小时数,返回0~23
setHours(value)设置小时数
getMinutes()获取分钟数,范围0~59
setMinutes(value)设置分钟数
getSeconds()获取秒数,范围0~59
setSeconds(value)设置秒数
getMilliseconds()获取毫秒数,范围0~999
setMilliseconds(value)设置毫秒数
getTime()获取从1970-01-01 00:00:00距离Date对象所代表时间的毫秒数
setTime(value)通过从1970-01-01 00:00:00计时的毫秒数来设置时间
// 根据Date对象获取时间日期
var date = new Date();    // 基于当前时间创建Date对象
// 结果:Fri Oct 06 2017 11:53:04 GMT+0800 (中国标准时间)
date.toString();
date.getFullYear();       // 结果:2017
date.getMonth();          // 结果:9
date.getDate();           // 结果:6

// 根据Date对象指定一个日期
// 方式1:分别传入年、月、日、时、分、秒(月的范围是0~11,即真实月份-1)
var date1 = new Date(2017, 9, 1, 11, 53, 4);
date1.toString();	// 返回结果:Sun Oct 01 2017 11:53:04 GMT+0800 (中国标准时间)
// 方式2:通过字符串传入日期和时间
var date2 = new Date('2017-10-01 11:53:04');
date2.toString();	// 返回结果:Sun Oct 01 2017 11:53:04 GMT+0800 (中国标准时间)

// 处理设置的日期不合理的情况,如将月份设为-1表示去年12月,月份为12表示明年1月
new Date('2017');        // Sun Jan 01 2017 08:00:00 GMT+0800 (中国标准时间)
new Date(2017, 9);       // Sun Oct 01 2017 00:00:00 GMT+0800 (中国标准时间)
new Date(2017, -1);      // Thu Dec 01 2016 00:00:00 GMT+0800 (中国标准时间)
new Date(2017, 12);      // Mon Jan 01 2018 00:00:00 GMT+0800 (中国标准时间)
new Date(2017, 0, 0);    // Sat Dec 31 2016 00:00:00 GMT+0800 (中国标准时间)

6.5 练习作业

  • 制作年历
    • 利用 prompt() 函数接收用户设置的年份。
    • 编写 calendar() 函数,根据指定的年份生成年历。
    • 设计并输出日历的显示样式。
    • 获取指定年份1月1日的星期值,获取每个月共有多少天。
    • 循环遍历每个月中的日期。
    • 将日期显示到对应的星期下面。

7 错误处理与代码调试

7.1 错误处理

在编写 JavaScript 程序时,经常会遇到各种各样的错误。

调用了不存在的方法、引用了不存在的变量等。

var o = {};
// 发生错误此句不会执行。
o.func();
console.log('test');

当发生错误时,JavaScript 引擎会抛出一个错误对象。

错误处理:try…catch 语句可对错误对象进行捕获,捕获后可以查看错误信息。

try {
    o.func();
    console.log('a');
} catch(e) {
    console.log(e);
}
console.log('b');

注意:

  • 如果 try 中有多行代码,只要其中一行出现错误,后面的代码都不会执行;如果错误已经被处理,则 catch 后面的代码会继续执行。由此可见,编写在 try 中的代码量应尽可能的少,从而避免错误发生时造成的影响。
  • 在以 Java 为代表的编程语言中,引入了异常(Exception)的概念,利用 try…catch 进行异常处理。JavaScript 错误处理的设计思想与之类似,因此也可以将 JavaScript 中的 try…catch 称为异常处理。

7.2 错误对象

在发生错误时,错误出现的位置、错误的类型,错误信息等数据,都会被封装起来,以一个对象的形式传递给 catch 语句,通过 catch(e)的方式来接收,其中 e 是错误对象的变量名。

错误对象会在函数之间传递

  • 当 try 中的代码调用了其他函数时,如果在其他函数中出现了错误,且没有使用 try…catch 处理时,程序就会停下来。
  • 将错误传递到调用当前函数的上一层函数,如果上一层函数仍然没有处理,则继续向上传递。
function foo1() {
    foo2();
    console.log('foo1');
}
function foo2() {
    var o = {}; 
    o.func();   // 发生错误
}

try {
    foo1();
} catch(e) {
    console.log('test');
}

手动抛出错误对象

除了在 JavaScript 程序出现错误时自动抛出错误对象,用户也可以使用 throw 关键字手动抛出错误对象。

try {
    var e1 = new Error('错误信息');
    throw e1;
} catch (e) {
    console.log(e.message);
    console.log(e1 === e);
}

7.3 错误类型

在 JavaScript 中,共有7种标准错误类型,每个类型都对应一个构造函数。当发生错误时,JavaScript 会根据不同的错误类型抛出不同的错误对象。

类型说明
Error表示普通错误,其余6种类型的错误对象都继承自该对象
EvalError调用eval()函数错误,已经弃用,为了向后兼容,低版本还可以使用
RangeError数值超出有效范围,如“new Array(-1)”
ReferenceError引用了一个不存在的变量,如“var a = 1; a + b;”(变量b未定义)
SyntaxError解析过程语法错误,如“{ ; }”“if()”“var a = new;”
TypeError变量或参数不是预期类型,如调用了不存在的函数或方法
URIError解析URI编码出错,调用encodeURI()、escape()等URI处理函数时出现

在通过 try…catch 来处理错误时,无法处理语法错误(SyntaxError)。如果程序存在语法错误,则整个代码都无法执行。

7.4 代码调试

在控制台中执行 JavaScript 代码

单步调试

调试按钮说明
暂停按钮暂停或继续执行脚本
下一步按钮1执行下一步。遇到函数时,不进入函数直接执行下一步
下一步按钮2执行下一步。遇到函数时,进入函数执行
跳出按钮跳出当前函数
停用按钮停用或启用断点
停止按钮是否暂停错误捕获
调试工具说明
Watch可以对加入监听列表的变量进行监听
Call Stack函数调用堆栈,可以在代码暂停时查看执行路径
Scope查看当前断点所在函数执行的作用域内容
Breakpoints查看断点列表
XHR Breakpoints请求断点列表,可以对满足过滤条件的请求进行断点拦截
DOM BreakpointsDOM断点列表,设置DOM断点后满足条件时触发断点
Global Listeners全局监听列表,显示绑定在window对象上的事件监听
Event Listener Breakpoints可断点的事件监听列表,可以在触发事件时进入断点

8 原型与继承

8.1 原型

如何让两个对象共同拥有一份相同的属性和方法?

可通过原型对象来解决。

原理:利用原型对象可以保存一些公共的属性和方法。当访问某个对象中的一个不存在的属性或方法时,会自动调用原型中的属性和方法。也就是说,基于原型创建的对象会自动拥有原型的属性和方法。

在JavaScript中,每定义一个函数,就随之有一个对象存在,函数通过prototype属性指向该对象。这个对象称之为原型对象,简称原型。

function Person() {}                   // 定义函数
console.log(typeof Person.prototype);  // 输出结果:object
  • Person函数的prototype属性指向的对象,就是Person的原型对象。
  • 在利用构造函数创建对象时,每个对象都默认与这个原型对象连接,连接后就可以访问到原型对象中的属性和方法。

8.2 继承

在现实生活中,继承一般指的是子女继承父辈的财产。

在JavaScript中,继承是在已有对象的基础上进行扩展,增加一些新的功能,得到一个新的对象。

在JavaScript中,提供了四种实现继承的方式。

  • 利用原型对象实现继承
  • 替换原型对象实现继承
  • 利用 Object.create() 实现继承
  • 混入继承
利用原型对象实现继承
function Person(name) {
    this.name = name;
}
Person.prototype.sayHello = function () {
    console.log('你好,我是' + this.name);
}

var p1 = new Person('Jim');
var p2 = new Person('Tom');
// 输出结果:你好,我是Jim
p1.sayHello();
// 输出结果:你好,我是 Tom
p2.sayHello();
替换原型对象实现继承
function Person() {}
Person.prototype = {
    sayHello: function () {
        console.log('你好,我是新对象');
    }
}
var p = new Person();
p.sayHello();

注意:在基于构造函数创建对象时,代码应写在替换原型对象之后,否则创建的对象仍然会继承原来的原型对象。

利用 Object.create() 实现继承
var obj = {
    sayHello: function(){
        console.log('我是一个带有sayHello方法的对象');
    }
};
// 将obj作为newObj对象的原型
var newObj = Object.create(obj);
混入继承
  • 混入就是将一个对象的成员加入到另一个对象中,实现对象功能的扩展。
  • 实现混入继承最简单的方法就是将一个对象的成员赋值给另一个对象。
var o1 = {};
var o2 = {name: 'Jim'};
o1.name = o2.name;     // o1继承o2的name属性
console.log(o1.name);  // 输出结果:Jim
  • 当对象的成员比较多时,如果为每个成员都进行赋值操作,会非常麻烦。
  • 因此编写一个函数专门实现对象成员的赋值,函数通常命名为mix(混合)或extend(扩展) 。
function extend(o1, o2) {
    for (var k in o2) {
        o1[k] = o2[k];
    }
}

var o1 = {name: 'Jim'};
var o2 = {age: 16, gender: 'male'};
extend(o1, o2);
console.log(o1.name);
console.log(o1.age);

混入式继承和原型继承还可以组合在一起使用,实现以对象的方式传递参数,或以对象的方式扩展原型对象的成员。

8.3 静态成员

静态成员:指由构造函数所使用的成员。

实例成员:指由构造函数创建的对象所使用的成员。

// 实例成员
function Person(name) {
    this.name = name;
    this.sayHello =  function() {
        console.log(this.name);
    };
}
var p = new Person('Tom');
console.log(p.name);
p.sayHello();	

// 静态成员
function Person(){}
Person.age = 123;
Person.sayGood = function() {
    console.log(this.age);
};
console.log(Person.age);
Person.sayGood();

实际开发中,对于不需要创建对象即可访问的成员,推荐将其保存为静态成员。

比如:构造函数的prototype属性就是一个静态成员,可以在所有实例对象中共享数据。

8.4 属性搜索原则

属性搜索原则只对属性的访问操作有效,对于属性的添加或修改操作,都是在当前对象中进行的。

8.5 原型链

在JavaScript中,对象有原型对象,原型对象也有原型对象,这就形成了一个链式结构,简称原型链。

对象的构造函数

在原型对象中,存在一个constructor属性,指向该对象的构造函数。

function Person() {}
Person.prototype.constructor === Person;

function Person() {}
new Person().constructor === Person;
对象的原型对象
  • 对象可以通过constructor属性访问构造函数。
  • 构造函数可以通过prototype属性访问原型对象。
  • 因此,对象.constructor.prototype即可访问对象的原型对象。
function Person() {}
new Person().constructor.prototype === Person.prototype;
函数的构造函数

由于函数本质上就是对象,所以函数也有构造函数。

function Person() {}
// 返回结果:function Function() { [native code] }
Person.constructor.toString();
Person.constructor === Function;   // 返回结果:true
String.constructor === Function;    // 返回结果:true
Number.constructor === Function;    // 返回结果:true
Object.constructor === Function;    // 返回结果:true
Function.constructor === Function;  // 返回结果:true

总结:

  • 在JavaScript中,自定义函数以及String、Number、Object等内置构造函数的构造函数都是Function函数。
  • Function函数的构造函数是Function自身。

还可以通过实例化Function构造函数的方式来创建函数。该构造函数的参数数量是不固定的,最后一个参数表示用字符串保存的新创建函数的函数体,前面的参数(数量不固定)表示新创建函数的参数名称。

// new Function('参数1', '参数2', …… '参数N', '函数体');
var func = new Function('a', 'b', 'return a + b;');
console.log(func(100, 200));		// 输出结果:300

func(100, 200);
// 相当于
var func = function(a, b) {
    return a + b;
}
原型对象的原型对象

对象.constructor.prototype,可访问对象的原型对象。

构造函数的prototype属性指向原型对象,原型对象的constructor属性又指回了构造函数,这就构成了一个循环。

通过这种方式无法访问到原型对象的原型对象。

因此,浏览器为对象增加了一个新的属性 __proto__,方法查看对象的原型。

适用范围:一些新版的浏览器,如火狐、Chrome等。

属性特点:由于其不是JavaScript原有属性,因此前后加两个下划线进行区分。

查看位置:在开发人员工具中方便地查看对象的原型。

function Person() {}
Person.prototype.__proto__;
function Person() {}
Person.prototype.__proto__ === Object.prototype;  // 返回结果:true
Object.prototype.__proto__;                       // 返回结果:null
Object.__proto__ === Function.prototype;          // 返回结果:true
  • 构造函数Person的原型对象的原型对象,是构造函数Object的原型对象。
  • 构造函数Object的原型对象的原型对象是null。
  • 构造函数Object的原型对象是构造函数Function的原型对象。
原型链的结构
  • 自定义函数,以及Object、String、Number等内置函数,都是由Function函数创建的,Function函数是由Function函数自身创建的。
  • 每个构造函数都有一个原型对象,构造函数通过prototype属性指向原型对象,原型对象通过constructor属性指向构造函数。
  • 由构造函数创建的实例对象,继承自构造函数的原型对象。通过实例对象的 __proto__ 属性可以直接访问原型对象。
  • 构造函数的原型对象,继承自Object的原型对象,而Object的原型对象的 __proto__ 属性为null。

在进行原型操作时,“对象.constructor.prototype”访问到的是该对象当前继承的原型对象的构造函数的原型对象,并不一定是实际构造函数的原型对象。

function Person() {}
function Func() {}
Person.prototype = new Func();
var p1 = new Person();
p1.constructor === Func;                      // 返回结果:true
p1.constructor.prototype === Func.prototype;  // 返回结果:true
p1.__proto__ === Person.prototype;            // 返回结果:true
instanceof 运算符

检测一个对象的原型链中是否含有某个构造函数的prototype属性所表示的对象。

返回值:布尔类型,存在返回true,否则返回false。

function Person() {}
var p1 = new Person();
console.log(p1 instanceof Person);  // 输出结果:true

// 更改构造函数的prototype属性
function Person() {}
function Func() {}
var p1 = new Person();
Person.prototype = new Func();
var p2 = new Person();
console.log(p1 instanceof Person);	// 输出结果:false
console.log(p2 instanceof Person);	// 输出结果:true

// 让当前Person.prototype在p1的原型链上
p1.__proto__.__proto__ = Person.prototype;
console.log(p1 instanceof Person);	// 输出结果:true

9 练习作业

  • 输入n,输出n天后的时间

  • 获取两个日期之间相差的天数

  • 使用Math对象随机数实现点名功能

  • 使用String对象实现,对任意字符串敏感字符替换功能

  • 使用工厂函数

    • 创建饮水机对象,饮水机对象有热水和冷水方法,以及饮水机位置属性
    • 创建三个怪物对象,并且输出怪物的名字、性别、等级,怪物执行攻击方法,输出“水球术”
    • 创建一个动漫对象,输出动漫的人物,爱好,性别,能力
  • var data = [
        { StudentNumber: 2022003, Chinese: 86, Math: 64, English: 80, TotalScore: "", Comment: "" },
        { StudentNumber: 2022004, Chinese: 78, Math: 99, English: 91, TotalScore: "", Comment: "" },
        { StudentNumber: 2022005, Chinese: 107.5, Math: 97, English: 70, TotalScore: "", Comment: "" },
        { StudentNumber: 2022006, Chinese: 112, Math: 61, English: 92, TotalScore: "", Comment: "" },
        { StudentNumber: 2022007, Chinese: 101, Math: 79, English: 104, TotalScore: "", Comment: "" },
        { StudentNumber: 2022008, Chinese: 71, Math: 72, English: 105, TotalScore: "", Comment: "" },
        { StudentNumber: 2022009, Chinese: 56, Math: 68, English: 61, TotalScore: "", Comment: "" },
        { StudentNumber: 2022010, Chinese: 98, Math: 83, English: 77, TotalScore: "", Comment: "" }
    ];
    
    • 以上是某班级一次考试的成绩表。请计算每个学生总成绩,并按总成绩排名。统计各单科成绩第一名,输出其成绩与学号。
    • 并根据总成绩计算每个学生成绩备注保存在 Comment 中,平均分72分以上为D,90分以上为C,100分以上为B,110分以上为A,72分以下为E
  • 有两个数组,一个数组arr是班级里所有的学员的名称,一个数组currentArr是提交了每日反馈的学员名单,请创建一种算法,把未提交每日反馈的学员筛选出来

  • 创建一个电脑对象,有颜色(color)、重量(weight)、品牌(brand)、型号(type)等属性,有看电影(watch)、听音乐(listen)、打游戏(play)和敲代码(coding)等方法。

    • 颜色、重量、品牌、型号是属性;看电影、听音乐、打游戏、敲代码是方法,在调用方法时分别打印 “我在看电影/听音乐/打游戏/敲代码”
    • 方法保存在原型对象中
  • 使用构造函数创建一个自定义对象,通过自定义对象生成指定行数、列数、宽度和高度的表格。

  • 表单生成器

    • 直接编写HTML表单虽然简单,但修改、维护相对麻烦。

    • 在线考试系统或问卷调查系统,题目和问题的录入的功能。

    • 利用对象保存表格数据。

    • 将表单项看成一个个对象。

    • 归纳表单项的结构模板,包括tag(标签名)、text(提示文本)、attr(标签属性)、option(选项)四个属性。

    • 按照表单数据保存的分析创建对象保存相关数据。

    • 编写FormBuilder对象,接收表单数据,并将结果显示到指定位置上。

    • 编写builder对象,根据传入的数据生成表单项。

    • 编写item对象,根据标签名称生成表单项。

    • var elements = [
          {tag: 'input', text: '用户名', attr: {type: 'text', name: 'username'}},
          {tag: 'input', text: '密码', attr: {type: 'password', name: 'userpass'}},
          {tag: 'input', text: '确认密码', attr: {type: 'password', name: 'userpass_confirm'}},
          {tag: 'input', attr: {type: 'submit', value: '提交'}}
      ];
      

      外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 定义一个对象,new 出来的每一个实例会生成一个彩色气泡,颜色、大小、位置、运动方向都随机设置

    • 颜色数组

    • var color = ["#f83600", "#FFF94C", "#0072ff", "#780206", "#7B920A", "#dc2430", "#A83279", "#00bf8f", "#FF512F", "#485563", "#061700", "#02AAB0"]
      

戏(play)和敲代码(coding)等方法。

  • 颜色、重量、品牌、型号是属性;看电影、听音乐、打游戏、敲代码是方法,在调用方法时分别打印 “我在看电影/听音乐/打游戏/敲代码”

  • 方法保存在原型对象中

  • 使用构造函数创建一个自定义对象,通过自定义对象生成指定行数、列数、宽度和高度的表格。

  • 表单生成器

    • 直接编写HTML表单虽然简单,但修改、维护相对麻烦。

    • 在线考试系统或问卷调查系统,题目和问题的录入的功能。

    • 利用对象保存表格数据。

    • 将表单项看成一个个对象。

    • 归纳表单项的结构模板,包括tag(标签名)、text(提示文本)、attr(标签属性)、option(选项)四个属性。

    • 按照表单数据保存的分析创建对象保存相关数据。

    • 编写FormBuilder对象,接收表单数据,并将结果显示到指定位置上。

    • 编写builder对象,根据传入的数据生成表单项。

    • 编写item对象,根据标签名称生成表单项。

    • var elements = [
          {tag: 'input', text: '用户名', attr: {type: 'text', name: 'username'}},
          {tag: 'input', text: '密码', attr: {type: 'password', name: 'userpass'}},
          {tag: 'input', text: '确认密码', attr: {type: 'password', name: 'userpass_confirm'}},
          {tag: 'input', attr: {type: 'submit', value: '提交'}}
      ];
      

      [外链图片转存中…(img-PiwLotxf-1695696232118)]

  • 定义一个对象,new 出来的每一个实例会生成一个彩色气泡,颜色、大小、位置、运动方向都随机设置

    • 颜色数组

    • var color = ["#f83600", "#FFF94C", "#0072ff", "#780206", "#7B920A", "#dc2430", "#A83279", "#00bf8f", "#FF512F", "#485563", "#061700", "#02AAB0"]
      
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

teayear

读后有收获可以获取更多资源

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值