JavaScript对象详解(六)

JavaScript对象是存储 键值对 的集合,表示属性和值的映射关系,这与Java中的HashMap很相似。JavaScript中所有对象都是由 Object 类派生的。

1、对象的使用

1.1、使用对象字面量法
// 一、通过对象字面量法来创建对象,键为字符串(如果不是字符串,底层也会自动转成字符串),
// 值可以为任何数据类型,未赋值的属性的值为undefined
let obj2 = {};  
let obj3 = {
    name: "Carrot",
    _for: "Max",//'for' 是保留字之一,使用'_for'代替
    details: {
        color: "orange",
        size: 12
    }
}
// 3、可以使用链式访问方式来访问属性
obj3.details.color; // orange
obj3["details"]["size"]; // 12
// 4、对象的销毁,给对象赋值null即可
obj3 = null;
// 5、动态添加属性和方法
//添加属性:
let obj4 = {};
obj4.name = "李太白";
obj4.age = 18;
//添加方法:
obj4.sex = function(){
	//方法代码
};
//访问方法:
obj4.sex; //或 obj4.sex();
/* 注意:
1. 属性名及方法名如果与对象原有的属性或方法同名,会重写属性的内容和方法的功能!
2. 方法是作为属性来存储的函数。
3. 不要把字符串、数值和布尔值声明为对象!(会增加代码的复杂性并降低执行速度)
*/
// 6、删除属性和方法,还能删除不是用var修饰的变量!
delete obj4.name;
delete obj4.sex;
let nb = 3;
delete nb;
// 7、遍历对象的属性
for(let key in 对象名){
	//使用:对象名[key]来获得对象中相应属性的值;
}
1.2、使用构造函数
// 二、通过构造函数来创建对象
function Person(name, age) { // 这是构造函数(也称为对象原型), You是对象实例
  // 使用 this 将传入函数的值赋给对象的属性
  this.name = name;
  this.age = age;
}
// new一个person对象,用You接收
let You = new Person('You', 24);
// 点表示法操作属性
You.name = 'Simon';
let name = You.name;
// 括号表示法操作属性
You['name'] = 'Simon';
let name = You['name'];
// 括号表示法可以被用来访问以预留关键字作为名称的属性的值,但是不会被编辑器优化
obj.for = 'Simon'; // 语法错误,因为 for 是一个预留关键字
obj["for"] = 'Simon'; // 工作正常
// 可以使用变量定义键
let user = prompt('what is your key?');
obj[user] = prompt('what is its value?');
1.3、使用 Object.create()
// 封装动物的属性和方法
var Animal = {
  type: "Invertebrates", // 属性默认值
  displayType : function() {  // 用于显示 type 属性的方法
    console.log(this.type);
  },
  myOtherMethod(params) { // 方法也能这样写
    // ...做其他事情
  }
}

// 创建一种新的动物——animal1
var animal1 = Object.create(Animal);
animal1.displayType(); // Output:Invertebrates

// 创建一种新的动物——Fishes
var fish = Object.create(Animal);
fish.type = "Fishes";
fish.displayType(); // Output:Fishes

2、继承与原型链

所有的 JavaScript 对象至少继承于一个对象,被继承的对象称作原型,并且继承的属性可通过 prototype 对象找到。
 
JavaScript 中每个实例对象(object)都有一个私有属性(称之为 __proto__)指向它的构造函数的原型对象(prototype)。该原型对象也有一个自己的原型对象(__proto__),层层向上直到一个对象的原型对象为 null。根据定义,null 没有原型,并作为这个 原型链 中的最后一个环节。
 
几乎所有 JavaScript 中的对象都是位于原型链顶端的 Object 的实例。

2.1、基于原型链的继承
2.1.1、继承属性
// 让我们从一个函数里创建一个对象 o,它自身拥有属性 a 和 b:
let f = function () {
   this.a = 1;
   this.b = 2;
}
/* 这么写也一样
function f() {
  this.a = 1;
  this.b = 2;
}
*/
let o = new f(); // {a: 1, b: 2}

// 在 f 函数的原型上定义属性
f.prototype.b = 3;
f.prototype.c = 4;

// 不要在 f 函数的原型上直接定义 f.prototype = {b:3,c:4};这样会直接打破原型链
// o.[[Prototype]] 有属性 b 和 c (其实就是 o.__proto__ 或者 o.constructor.prototype)
// o.[[Prototype]].[[Prototype]] 是 Object.prototype.
// 最后 o.[[Prototype]].[[Prototype]].[[Prototype]] 是 null
// 这就是原型链的末尾,即 null,根据定义,null 就是没有 [[Prototype]]。

// 综上所述,整个原型链如下:
// {a:1, b:2} ---> {b:3, c:4} ---> Object.prototype---> null

console.log(o.a); // 1
// a 是 o 的自身属性吗?是的,该属性的值为 1

console.log(o.b); // 2
// b 是 o 的自身属性吗?是的,该属性的值为 2
// 原型上也有一个'b'属性,但是它不会被访问到。
// 这种情况被称为"属性遮蔽 (property shadowing)"

console.log(o.c); // 4
// c 是 o 的自身属性吗?不是,那看看它的原型上有没有
// c 是 o.[[Prototype]] 的属性吗?是的,该属性的值为 4

console.log(o.d); // undefined
// d 是 o 的自身属性吗?不是,那看看它的原型上有没有
// d 是 o.[[Prototype]] 的属性吗?不是,那看看它的原型上有没有
// o.[[Prototype]].[[Prototype]] 为 null,停止搜索
// 找不到 d 属性,返回 undefined
2.1.2、继承方法
// 当继承的函数被调用时,this 指向的是当前继承的对象,而不是继承的函数所在的原型对象。
var o = {
  a: 2,
  m: function(){
    return this.a + 1;
  }
};

console.log(o.m()); // 3  ,当调用 o.m 时,'this' 指向了 o

var p = Object.create(o); // p 是一个继承自 o 的对象

p.a = 4; // 创建 p 的自身属性 'a'
console.log(p.m()); // 5
// 调用 p.m 时,'this' 指向了 p
// 又因为 p 继承了 o 的 m 函数
// 所以,此时的 'this.a' 即 p.a,就是 p 的自身属性 'a'
2.2、不同方式所生成的原型链
2.2.1、使用语法结构
var o = {a: 1};
// o 这个对象继承了 Object.prototype 上面的所有属性
// o 自身没有名为 hasOwnProperty 的属性,hasOwnProperty 是 Object.prototype 的属性
// 因此 o 继承了 Object.prototype 的 hasOwnProperty, Object.prototype 的原型为 null
// 原型链如下:
// o ---> Object.prototype ---> null

var a = ["yo", "whadup", "?"];
// 数组都继承于 Array.prototype ,(Array.prototype 中包含 indexOf, forEach 等方法)
// 原型链如下:
// a ---> Array.prototype ---> Object.prototype ---> null

function f(){
  return 2;
}
// 函数都继承于 Function.prototype ,(Function.prototype 中包含 call, bind 等方法)
// 原型链如下:
// f ---> Function.prototype ---> Object.prototype ---> null
2.2.2、使用构造器
function Graph() {
  this.vertices = [];
  this.edges = [];
}

Graph.prototype = {
  addVertex: function(v){
    this.vertices.push(v);
  }
};

var g = new Graph();
// g 是生成的对象,他的自身属性有 'vertices' 和 'edges'。
// 在 g 被实例化时,g.[[Prototype]] 指向了 Graph.prototype。
2.2.3、使用Object.create()
var a = {a: 1};
// a ---> Object.prototype ---> null

// ES5新增的方法,可以调用这个方法来创建一个新对象。新对象的原型就是调用 create 方法时传入的第一个参数:
var b = Object.create(a);
// b ---> a ---> Object.prototype ---> null
console.log(b.a); // 1 (继承而来)

var c = Object.create(b);
// c ---> b ---> a ---> Object.prototype ---> null

var d = Object.create(null);
// d ---> null
console.log(d.hasOwnProperty); // undefined,因为 d 没有继承 Object.prototype
2.2.4、使用 class 关键字
// class是ES6新增的关键字(class, constructor,static,extends,super),它仍然是基于原型的
class Polygon {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
}

class Square extends Polygon {
  constructor(sideLength) {
    super(sideLength, sideLength);
  }
  get area() {
    return this.height * this.width;
  }
  set sideLength(newLength) {
    this.height = newLength;
    this.width = newLength;
  }
}

var square = new Square(2);
2.3、实现继承的6种方式
2.3.1、原型链继承
function Parent1() { // 父类
  this.name = 'parent1';
  this.play = [1, 2, 3];
}
function Child1() { // 子类
  this.type = 'child1';
}
// 将子类Child1原型指向父类Parent1
Child1.prototype = new Parent1(); 
// 此时Child1可以访问父类的属性,但是对于多个子类来说,父类的属性与方法是共享的!
let child1 = new Child1();
2.3.2、构造函数继承
function Parent1() { // 父类
  this.name = 'parent1';
}
Parent1.prototype.getName = function () { // 给父类原型添加属性
  return this.name;
}
function Child1() {
  // call()用于调用父类构造函数,this后面可跟属性列表;
  // 这种方式只能继承父类的实例属性和方法,不能继承父类的原型属性或者方法。
  Parent1.call(this);
  this.type = 'child1';
}
let child = new Child1();
// 正常运行
console.log(child);
// 运行报错,因为child没有getName方法
console.log(child.getName());
2.3.3、组合继承(原型链+构造函数)
// 这种方式会导致多调用一次构造函数,性能开销会变大
function Parent3() { // 父类
  this.name = 'parent3';
  this.play = [1, 2, 3];
}
Parent3.prototype.getName = function () { // 给父类原型添加属性
  return this.name;
}
function Child3() {
  // 第二次调用父类构造函数
  Parent3.call(this);
  this.type = 'child3';
}
// 第一次调用父类构造函数
Child3.prototype = new Parent3();
// 手动挂上构造器,指向自己的构造函数
Child3.prototype.constructor = Child3;

let c1 = new Child3();
let c2 = new Child3();
c1.play.push(4);
console.log(c1.play, c2.play); // 不互相影响
console.log(c1.getName()); // 正常输出'parent3'
console.log(c2.getName()); // 正常输出'parent3'
2.3.4、原型式继承
// 这种方式,多个实例的引用类型属性指向相同的内存,所以存在篡改的可能
let parent4 = { // 父类
  name: 'parent4',
  friends: ['p1', 'p2', 'p3'],
  getName: function () {
    return this.name
  },
}
// create方法返回一个继承parent4的对象,person的原型将指向parent4
let person = Object.create(parent4);
// 子类添加和修改属性
person.name = 'Tom';
person.friends.push('jerry');
// 定义一个用作比较的变量,并修改属性
let person2 = Object.create(parent4);
person2.friends.push('lucy');
// 测试输出
console.log(person.name); // Tom
console.log(person.name === person.getName()); // true
console.log(person2.name); // parent4
console.log(person.friends); // ['p1', 'p2', 'p3', 'jerry', 'lucy']
console.log(person2.friends); // ['p1', 'p2', 'p3', 'jerry', 'lucy']
2.3.5、寄生式继承
/*
使用原型式继承可以获得一份目标对象的浅拷贝,然后利用这个浅拷贝的能力再进行增强,添加一些方法,
这样的继承方式就叫作寄生式继承。对于普通对象的继承方式来说,寄生式继承相比于原型式继承,
在父类基础上添加了更多的方法。
*/
let parent5 = { // 父类
  name: 'parent5',
  friends: ['p1', 'p2', 'p3'],
  getName: function () {
    return this.name
  },
}
function clone(original) { // 封装一个浅拷贝函数
  let clone = Object.create(original); // 得到继承于original的对象
  clone.getFriends = function () { // 返回的是clone对象的friends属性
    return this.friends; 
  }
  return clone;
}

let person = clone(parent5); 
console.log(person.getName()); // parent5
console.log(person.getFriends()); //['p1', 'p2', 'p3']
2.3.6、寄生组合式继承
/*
结合原型式提及的继承方式,解决普通对象的继承问题,不同实例的引用类型属性不存在篡改的可能,
通过改造前几种方式,得出了寄生组合式的继承方式,这也是所有继承方式里面相对最优的继承方式
*/
function Parent6() { // 父类
  this.name = 'parent6';
  this.play = [1, 2, 3];
}
Parent6.prototype.getName = function () { // 给父类原型添加属性
  return this.name;
}
function Child6() {
  Parent6.call(this); // 继承
  this.friends = 'child5';
}
// child原型指向继承parent原型的对象,child原型的构造函数又指向child的构造函数
function clone(parent, child) {
  child.prototype = Object.create(parent.prototype);
  child.prototype.constructor = child;
}

clone(Parent6, Child6); // 进行引用转换操作

Child6.prototype.getFriends = function () { // 子类原型添加个方法
  return this.friends;
}

let person = new Child6();
console.log(person); 
console.log(person.getName() + " " + person.getFriends()); // parent6 child5
let p2 = new Child6();
p2.play.push(4);
console.log(person.play); // [1, 2, 3]
console.log(p2.play); // [1, 2, 3, 4]
2.3.7、总结

请添加图片描述

 
构造函数继承:只能继承父类的实例属性和方法,不能继承父类的原型属性或者方法;
 
原型链继承:子类可以访问父类的属性,但是对于多个子类来说,父类的属性与方法是共享的;
 
组合继承:这种方式会导致多调用一次构造函数,性能开销会变大;
 
原型式继承:这种方式,多个实例的引用类型属性指向相同的内存,所以存在篡改的可能;
 
寄生式继承:相比原型式继承,在父类基础上添加了更多方法;
 
寄生组合式继承:结合前几种方式的优缺点,解决了普通对象的继承问题,和原型式的篡改问题。


3、包装类

名称描述
Number()数字的包装类
String()字符串的包装类
Boolean()布尔值的包装类

4、Math 对象的常用方法

方法名称描述
Math.round()可以将一个数字四舍五入为整数
Math.max()取一组数中的最大值
Math.min()取一组数中的最小值
Math.random()0-1 之间的随机数(包含 0,不包含 1)

5、Date 对象的常用方法

方法名称描述
new Date()当前时间的日期对象
getDate()返回一个月中的某一天 (1 ~ 31)
getDay()返回一周中的某一天 (0 ~ 6)
getMonth()返回月份 (0 ~ 11)
getFullYear()返回年份
getHours()返回小时 (0 ~ 23)
getMinutes()返回分钟数(0-59)
getSeconds()返回秒数(0-59)
getTime()将日期对象变为时间戳(返回 1970 年 1 月 1 日至今的毫秒数)
parse()将日期对象变为时间戳(返回 1970 年 1 月 1 日至今的毫秒数),最后三位是 000

6、正则表达式

正则表达式是用于匹配字符串中字符组合的模式。在 JavaScript 中,正则表达式也是对象。这些模式被用于 RegExpexectest 方法,以及 StringmatchmatchAllreplacesearchsplit 方法。
 
官方教程:Regular_Expressions

6.1、创建正则表达式
var patt=/pattern/attributes;
//或调用RegExp的构造函数
var patt=new RegExp(pattern,attributes);
/*
- pattern:是一个字符串,指定了正则表达式的模式或其他正则表达式。
- attributes:是一个可选的字符串,包含属性 "g"、"i" 和 "m",分别用于指定全局匹配、区分大小写的匹配和多行匹配。
*/
//当使用构造函数创造正则对象时,需要常规的字符转义规则(在前面加反斜杠 \)
var re = new RegExp("\\w+");
var re = /\w+/;
6.2、常用方法
方法描述
exec一个在字符串中执行查找匹配的 RegExp 方法,它返回一个数组(未匹配到则返回 null)。
test一个在字符串中测试是否匹配的 RegExp 方法,它返回 true 或 false。
match一个在字符串中执行查找匹配的 String 方法,它返回一个数组,在未匹配到时会返回 null。
matchAll一个在字符串中执行查找所有匹配的 String 方法,它返回一个迭代器(iterator)。
search一个在字符串中测试匹配的 String 方法,它返回匹配到的位置索引,或者在失败时返回 -1。
replace一个在字符串中执行查找匹配的 String 方法,并且使用替换字符串替换掉匹配到的子字符串。
split一个使用正则表达式或者一个固定字符串分隔一个字符串,并将分隔后的子字符串存储到数组中的 String 方法。
6.3、使用案例
var myArray = /d(b+)d/g.exec("cdbbdbsbz");
// 和 "cdbbdbsbz".match(/d(b+)d/g); 相似。
// 但是 "cdbbdbsbz".match(/d(b+)d/g) 输出数组 [ "dbbd" ],
// 而 /d(b+)d/g.exec('cdbbdbsbz') 输出数组 [ "dbbd", "bb", index: 1, input: "cdbbdbsbz" ].

var myRe = new RegExp("d(b+)d", "g");
var myArray = myRe.exec("cdbbdbsbz");

var myRe2 = /d(b+)d/g;
var myArray2 = myRe.exec("cdbbdbsbz"); //这样也ok

console.log(myRe2.lastIndex); // 5
console.log(/d(b+)d/g.lastIndex); // 0 ,这样不行哦

var re = /(\w+)\s(\w+)/;
var str = "John Smith";
var newstr = str.replace(re, "$2, $1"); // 使用子匹配替换str字符串
console.log(newstr); // Smith John
// 下面这个姓名字符串包含了多个空格和制表符,且在姓和名之间可能有多个空格和制表符。
var names = "Orange Trump ;Fred Barney; Helen Rigby ; Bill Abel ; Chris Hand ";

var output = ["---------- 原始字符串\n", names + "\n"];

// 准备两个模式的正则表达式放进数组里。分割该字符串放进数组里。
// 匹配模式:匹配一个分号及紧接其前后所有可能出现的连续的不可见符号。
var pattern = /\s*;\s*/;
// 把通过上述匹配模式分割的字符串放进一个叫做 nameList 的数组里面。
var nameList = names.split(pattern);

// 新建一个匹配模式:匹配一个或多个连续的不可见字符及其前后紧接着由
// 一个或多个连续的基本拉丁字母表中的字母、数字和下划线组成的字符串
// 用一对圆括号来捕获该模式中的一部分匹配结果。捕获的结果稍后会用到。
pattern = /(\w+)\s+(\w+)/;

// 新建一个数组 bySurnameList 用来临时存放正在处理的名字。
var bySurnameList = [];

// 输出 nameList 的元素并且把 nameList 里的名字
// 用逗号接空格的模式把姓和名分割开来然后存放进数组 bySurnameList 中。
//
// 下面的这个替换方法把 nameList 里的元素用 $2, $1 的模式
//(第二个捕获的匹配结果紧接着一个逗号一个空格然后紧接着第一个捕获的匹配结果)替换了
// 变量 $1 和变量 $2 是上面所捕获的匹配结果。

output.push("---------- 按正则表达式拆分后");

var i, len;
for (i = 0, len = nameList.length; i < len; i++) {
  output.push(nameList[i]);
  bySurnameList[i] = nameList[i].replace(pattern, "$2, $1");
}

// 输出新的数组
output.push("---------- 反转名称");
for (i = 0, len = bySurnameList.length; i < len; i++){
  output.push(bySurnameList[i]);
}

// 根据姓来排序,然后输出排序后的数组。
bySurnameList.sort();
output.push("---------- 排序");
for (i = 0, len = bySurnameList.length; i < len; i++){
  output.push(bySurnameList[i]);
}

output.push("---------- 结束了");
console.log(output.join("\n"));
6.4、修饰符
修饰符描述
i执行对大小写不敏感的匹配。
g执行全局匹配(查找所有匹配而非在找到第一个匹配后停止)。
m执行多行匹配。
6.5、方括号表示法

方括号用于查找某个范围内的字符

表达式描述
[abc]查找方括号之间的任何字符。
[^abc]查找任何不在方括号之间的字符。
[0-9]查找任何从 0 至 9 的数字。
[a-z]查找任何从小写 a 到小写 z 的字符。
[A-Z]查找任何从大写 A 到大写 Z 的字符。
[A-z]查找任何从大写 A 到小写 z 的字符。
[adgk]查找给定集合内的任何字符。
[^adgk]查找给定集合外的任何字符。
(red|blue|green)查找任何指定的选项。
6.6、元字符

元字符(Metacharacter)是拥有特殊含义的字符

元字符描述
d匹配一个数字字符
D匹配一个非数字字符
w匹配字母、数字或者下划线,类似于[A-Za-z0-9_]
W匹配除字母、数字或者下划线以外的字符。等价于[^A-Za-z0-9_]
s匹配一个空白字符(空格、制表符和换行符)
.匹配任意一个字符
^匹配开头
$匹配结尾
6.7、量词
量词描述
*匹配前面的子表达式零次或多次,等价于: {0,}
+匹配前面的子表达式一次或多次,等价于: {1,}
?匹配前面的子表达式零次或一次,等价于: {0,1}
{n}n 是一个正整数,表示匹配 n 次
{n,}n 是一个正整数,表示至少匹配 n 次
{n,m}n 和 m 是一个正整数,最少匹配 n 次且最多匹配 m 次

上一篇文章下一篇文章
JavaScript数组(五)JavaScript语句(七)
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值