web前端攻城狮 学习笔记——JavaScript基础

要来啃最硬的骨头了,在这之前,我买了本JS的红宝书《JavaScript高级程序设计》(第4版),要加油!

1 JS语言结构
1.1 字符集
JS⽀持Unicode字符集,你可以使⽤⼏乎所有的拉丁⽂、亚洲符号⽂字和汉字来书写程序。Unicode是计算机科学领域关于⽂本表示的⼀项标准,⽤于处理世界上所有⽂字和符号。Unicode包括字符集和编码⽅案。Unicode字符集⼏乎囊括所有的拉丁⽂、汉字和其他常⽤⽂字符号以及颜⽂字(emoji)。

const CARD_POINTS = ['A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'K', 'Q'];
const CARD_SUITS = [' ', ' ', ' ', ' '];
function getRandomItem(list) {
return list[Math.floor(Math.random(list.length))];
} 
function getRandomCard() {
const point = getRandomItem(CARD_POINTS);
const suit = getRandomItem(CARD_SUITS);
}

1.2 符号
符号(Tokens)包括标识符、字⾯量、标点符及模板
符号组成语句和语句块
下面的代码中,function, greeting, message,return 等都是标识符,(, ), {, }, +, = 等都是标点符,'Hello ', 'world', 'everyone' 等都是字⾯量。

function greeting(message = 'world') {
return 'Hello ' + message;
}
const message = greeting('everyone');
console.log(message);

1.2.1 标识符
由Unicode组成的符号,它可以是变量名、函数名以及保留字
1.2.2 字面量
直接表示程序中的某些数据的符号,包括Null、Boolean、Number、String以及正则表达式(RegularExpression)等
1.2.3 标点符
表达式中连接标识名与字⾯量的运算符以及表示结构的花括号、⼩括号、中括号、点、分号、逗号、冒号等。
1.2.4 模板
模板字⾯量⼀种JavaScript⽀持的特殊的字符串语法
是JS支持的特殊的符号

语句和语句块
语句由Token组成
分号代表语句的终⽌
⼀个或多个语句可以组成语句块
语句以花括号标记起始和结束

function sum(n) {
   let ret = 0;
   let i = 1;
   while(i<=n) {
      ret += i;
      i++;
      }
   return ret;
}
console.log(sum(10));

符号组成语句和语句块,
function内和while内都是语句块

换行
通常符号与符号之间也能插⼊⼀个或多个换⾏符,
但有⼀些特殊情况不允许换⾏:
return和返回值之间
break/continue和label名之间
变量和++--后缀运算符之间
throw 和异常对象之间
箭头函数的参数列表和箭头=>之间
yield和迭代值之间
async和异步函数声明、函数表达式、⽅法名之间

缩进
JS书写一般使用到缩进,如上面的代码
缩进让代码看起来更加整⻬,便于阅读和理解
缩进⼀般⽤Tab或若⼲个空格
在⼀个项⽬中,缩进规则应当统⼀

注释
JavaScript采⽤与C和Java语⾔⼀致的注释格式,分
别⽤//表示单⾏注释,⽤/*...*/表示多⾏注释。

作用域
语句块:块级作⽤域
函数:函数作⽤域
模块:模块作⽤域(ES Modules)

//块级作⽤域
let i = 1;
{
   let i = 2;
   console.log(i); // 2
}
console.log(i); // 1
//函数作⽤域
const bar = 'bar';
function foo() {
   const bar = 'foobar';
   console.log(bar);
}
foo(); // foobar
console.log(bar); // bar

变量声明
在这里插入图片描述
var
老版本ES5及之前的早期版本
⽆块级作⽤域
声明提升(hoist)

console.log(a==undefined); // true
var a = 10;

function foo(){
    console.log(a, i); //undefined
    var a = 20;
    for(var i = 0; i < a; i++){
        //do sth.
    }
    console.log(a, i); ێё 20, 20
} 
foo();

a和i都是没有声明

let
块级作⽤域
暂存死区(DTZ)

{
   let x = 10;
   console.log('x is ' + x);// x is 10
} 
console.log(typeof x); //error
let x = 10;
function foo(){
   console.log(x); //undefined
   var x = 20;
   return x * x;
} 
console.log(foo()); //400

const
块级作⽤域
暂存死区(DTZ)
绑定值不可变

const BUFFER_SIZE = 1024;
const buffer = new ArrayBuffer(BUFFER_SIZE);
console.log(buffer);
const data = new Uint16Array(buffer);
const data2 = new Uint8Array(buffer);
data[0] = 0ďff06;
console.log(data2[0], data2[1]);

1.3 空白符
空⽩符指Token之间可以插⼊的所有字符,包括空格、换⾏和制表符

2 JS数据类型
JS是弱类型动态类型的语言,在声明的时候不给变量指定类型。
强类型和弱类型中,强类型是很难类型转换的,其关系如下图:
在这里插入图片描述
原始类型和⾮原始类型
原始类型有7类,非原始类型有2类(function和普通对象)
在这里插入图片描述
typeof
原始类型和⾮原始类型

console.log(
    typeof null, //object,JS的历史遗留类型
    typeof undefĐned, //undefined
    typeof 123, //number
    typeof 'abc', //string
    typeof true, //boolean
    typeof Symbol(), //symbol
    typeof 2n, //bigint
    typeof Object(), //object
);
function add(x, y) {
    return x + y;
} 
console.log(typeof add); // function

隐式类型转换
弱类型才可以允许隐式类型转换。

字符串与数值相加时,数值被转转换为字符串
字符串参与⾮加法数学运算时,字符串被转换为数值
布尔值与数值进⾏运算时,true视为1,false视为0
布尔值与字符串进⾏相加时,布尔值被视为字符串
更多规则参考 ECMA-262

const a = 10, b = 'abc', c = 1;
console.log(a + b + c); //10abc1
const a = 123, b = '456', c = 1
console.log(a + b - c); //123455
const a = 123, b = 'abc', c = 1;
console.log(a + b - c); //NaN
const a = true, b = false;
console.log(a + 1, b * 3); //2 0
const a = true, b = false;
console.log(a + '', b + 'foobar');
//'true', falsefoobar

=====
值⽤==操作符⽐较时,会触发隐式类型转换,这样代码可能比较危险
值⽤===操作符⽐较时,不会触发隐式类型转换
⼀般原则是除了与null⽐较外,尽量⽤===
具体⽐较规则参考 ECMA-262

console.log(100 == ' 1e2'); //true,触发规则转化成隐式,1e2就是100的科学计数法
console.log(true == '1', false ==0); //true true

let foo, bar = null;
console.log(foo == null,
bar == null,
foo == undefined,
bar == undefined); //true true true true
console.log(true == 1, true === 1); //true false,后者无法转换成隐式类型

显式类型转换
最好使用显示类型转换
通过调⽤⽅法NumberStringBoolean
可以将值显式转换类型

console.log(Number('123')  ===123); // true
console.log(String(0*f) ==='15'); // true
console.log(Boolean(null) ===false); //true

值类型和引⽤类型
原始类型默认是值类型
⾮原始类型默认是引⽤类型

let x = 20, y = 30;
function foo(a, b){
    a++;
    b++;
    console.log([a, b]); //21 31
}
foo(x, y);
console.log([x, y]); //20 30访问外面的值还是以前的值
const obj = {x: 20, y: 30};
function foo2(obj){
    obj.x++;
    obj.y++;
    console.log(obj);// 21 31
}
foo2(obj);
console.log(obj); // {x: 21, y: 31}传了引用,相当于地址空间,访问外面的值会被修改

3 JS原始类型
Null和Undefined
Null和Undefined是JavaScript中的两种原始类型,它们分别只有⼀个值。
Null的值是null
Undefined的值是undefined
在⾮严格⽐较下,null == undefined
严格条件下各是各的
undefined一般表示不存在或未定义,null是我们给对象指定的,有的时候经常会混用。

let foo;//变量标识符被声明⽽没有初始化
console.log(foo); //undefined
function bar(a, b){
    return [a, b]
}
//bar函数的第⼆个形参没有传入实参
console.log(bar(1)); //[1, undefĐned]

let sum = 0;
function addSum(num) {
    sum += num;
}
//addSum没有return值
console.log(addSum(10)); //undefined
//访问p对象不存在的z属性
let p = {x:1, y:2};
console.log(p.z); //undefined
let foo = null;
console.log(foo); //null

Number
是符合IEEE 754标准的64位浮点数
整数有⼆进制、⼋进制、⼗进制和⼗六进制表示法
可以⽤科学记数法表示
精确表示的整数范围从-2^53+12^53-1,表示的位数是有限制的
常量Number.MAX_SAFE_INTEGER就是2^53-1

0
7
-3
0b101 //⼆进制表示的5,0b前缀表示⼆进制
0o777 //⼋进制表示的511,0o前缀表示⼋进制
-0ď7f //⼗六进制表示的-127,0x前缀表示⼗六进制
3e9 //科学计数法表示3000000000
let n1 = 10,
    n2 = Number.MAX_SAFE_INTEGER,
    n3 = 1.2;
console.log(Number.isSafeInteger(n1),
Number.isSafeInteger(n2),
//n3是浮点数不是整数, 所以结果也为false
Number.isSafeInteger(n3),
Number.isSafeInteger(n2 + 1));
//true, true, false, false

浮点数
浮点数可以表示⼩数
0.3可以写成.3
规范规定浮点数的整数部分如果是0,0可以省略。
浮点数也可以使⽤科学计数法。
最⼤浮点数 Number.MAX_VALUE
最⼩浮点数 Number.MIN_VALUE
浮点数精度 Number.EPSILON
⽆穷⼤数 Infinity

.3 //相当于0.3
3.14159265
6.62e-34

Number.MAX_VALUE //1.7976931348623157e+308
Number.MIN_VALUE //5e-324
Number.EPSILON //2.220446049250313e-16,能够精确表示的数值
let n1 = Number.MIN_VALUE,
    n2 = 1,
    n3 = n2 + n1,
    n4 = n2 + Number.EPSILON;
console.log(n1 > 0, n2 < n3, n2 < n4);
//true, false, true
console.log(Number.MAX_VALUE * 2, 
-Number.MAX_VALUE * 2,
1 / 0,
-1 / 0);
// Infinity, -Infinity, Infinity, -Infinity

运算精度问题

浮点数运算存在精度问题
不可⽤相等⽐较浮点数!
不可⽤相等⽐较浮点数!
不可⽤相等⽐较浮点数!

console.log(0.1 + 0.2); //0.30000000000000004,不可避免的精度问题
console.log(0.1 + 0.2  === 0.3); // false
function floatEqual(a, b) {
return Math.abs(a - b) < Number.EPSILON;
} 
console.log(floatEqual(0.1 + 0.2, 0.3));
//true

NaN
符号NaN表示Not-a-Number。在计算的过程中,遇到⽆法表示为数值的情况,计算结果就会是NaN
如果两个数值是NaN,它们的⽐较结果是不等的
Number.is NaN 判断
⽤ Object.is ⽐较

const n1 = Math.sqrt(-1), //负数开平⽅
//与⼀个不能转为数值的字符串进⾏运算
n2 = 3 * "abc";
console.log(n1, n2); //NaN, NaN,NaN不能是相等的
console.log(n1 == n2); //false
console.log(Number.isNaN(n1), Number.isNaN(n2));
//true, true
console.log(Object.is(n1, n2)); //true

+0与-0
数值0有+0和-0两种形态,这两个值如果⽐较的话是相等的。但是如果它们作为除数进⾏运算,分别会得到+Infinity和-Infinity。
同样,如果⼀个有限的正数除以Infinity和-Infinity分别得到+0和-0。

const a = 0, b = -0;
console.log(a === b, 1 / a, 1 / b);
// true Infinity -Infinity
console.log(1 / Infinity, 1 / -Infinity); //0 -0

Boolean
Boolean类型表示逻辑真和逻辑假,它只有两个可选的值,分别是字⾯量true和false。

JS的⽐较操作符返回布尔类型的结果
做布尔判断时存在隐式类型转换
+0、-0、NaN、空串、undefined、null 转为 false,其他值是true

const TRUE = true,
FALSE = false;
console.log(typeof TRUE, typeof FALSE);
//boolean, boolean
if(true) {
console.log('Something should print');
} 
while(false) {
console.log('Something should not print');
} 
const result = (6 * 7 == 42);
console.log(result); //true

String
JS使⽤⼀对单引号 ’ 或⼀对双引号 " 来表示字符串,单引号和双引号中间不能有换⾏符。

⽀持特殊转义符和Unicode转义符
由于HTML标签属性⽤双引号,所以JS字符串通常推荐⽤单引号

var text = 'This is a text.';
var html = '<p class="sth">a <em>paragraph</em></p>';
console.log(text);
console.log(html);
var text2 = '我所做的馅饼\n是全天下\n最好吃的';
console.log(text2);
var text3 = 'if(a){\n\tconsole.log(b);\n}';
console.log(text3);
var text4 = '\u5947\u821e\u56e2';
console.log(text4);

处理字符
字符串可以使⽤spread操作符展开成字符数组。可以使⽤codePointAt⽅法来获得某位字符的Unicode码位。

Unicode码位以多字节Unicode编码表示⼀个字符
String.fromCodePoint⽅法可以将码位还原为字符串

const str1 = '';//这里应该是东和南两个风头
const arr = [...str1];
console.log(arr);
console.log(str1.codePointAt(0));
const str2 = String.fromCodePoint(126978, 126979);//码位还原为字符串
console.log(str1+str2);

在这里插入图片描述
类型转换
字符串可以与其他类型数据相互操作。

+操作符触发其他类型的隐式类型转换
Number.parseInt与Number.parseFloat
显式类型转换
对象的toString⽅法

console.log([1+2, '1'+2, '1'-2]);
// [3, "12", -1]
console.log(Number.parseInt('100abc', 2)); //4,尝试转换成二进制
console.log(Number('0b100')); //4
console.log(Number.parseFloat('12.3e10xx'));// 123000000000,xx不认识则会删掉
var foo = { //对象的 toString ⽅法
    toString(){
        return 'foo';
    }
};
console.log(foo + ' bar'); //foobar

常⽤操作
字符串内置常⽤操作⽅法。

字符串连接
⼤⼩写转换
逆序
截取
查找

const a = 'hello';
const b = 'WORLD';
const c = '!';
console.log(a + ' ' + b + c); //字符串连接
//hello WORLD!
console.log(a.toUpperCase() + ' ' + b.toLowerCase() + c)
//HELLO world!
console.log(a.split('').reverse().join('')); //逆序字符串
//olleh
console.log(a.slice(2,3), a.substr(2,3)); //截取⼦串
//l llo
const d = a + ' ' + b + c;
console.log(d.indexOf(b)); //字符串查找
//6
console.log(d.replace(a, a.toUpperCase()));//替换,或转换正则表达式
//"HELLO WORLD!"

多⾏⽂本
ES6之后,JS⽀持以⼀对反引号`表示多⾏⽂本,同时也是模板字符串。

多⾏⽂本保留空⽩符
多⾏⽂本是模板字符串,可以解析和替换内容

const tpl1 = `我所做的馅饼
    是全天下
    最好吃的`;
console.log([tpl1, typeof tpl1]);
{
    let who = '⽉影', what = '⽉饼';
    const tpl2 = `${who}所做的${what}
        是全天下
        最好吃的`;
    console.log(tpl2);
}

Symbol(新的原始数据类型)
ES6及之后的版本中引⼊的新原始数据类型。Symbol可以创建唯⼀标识。

作为对象的key
Symbol.for 创建全局的
Symbol.keyFor

const id1 = Symbol('foo');
const id2 = Symbol('foo');
console.log(id1 === id2); //false,创建的时候symbol是唯一的
const foo = Symbol.for('foobar'),
bar = Symbol.for('foobar');
console.log(foo === bar); //true,这个时候是全局的,可以相等
const foo = Symbol.for('foobar');
console.log(Symbol.keyFor(foo)); //foobar,把描述符取出来

私有属性
新的语⾔标准中private field有些不理想,所以也可以采⽤Symbol来定义私有属性。

const size = Symbol('size');//起到属性私有的作用,因此内部可以放心使用私有属性
class Collection {
    constructor() {
        this[size] = 0;
    } 
    add(item) {
        this[this[size]] = item;
        this[size]++} 
    static sizeOf(instance) {
        return instance[size];
    }
}

内置Symbol
ES6内置了⼀些有⽤的Symbol,可以⽤来控制对象的⼀些内部⾏为。

Symbol.iterator 定义对象的迭代行为
Symbol.toPrimitive 定义对象的对原始类型的转换
Symbol.toStringTag 定义对象的本身字符串的标签

class Path {
    constructor() {
        this._points = [];
    }
    add(...points) {
        this._points.push(...points);
    }
    *[Symbol.iterator]() {//迭代器
        yield *this._points;
    }
    get length() {
        return this._points.length;
    }
} 
const path = new Path();
path.add([1, 1], [1, 2], [2, 3]);
console.log(path.length);
for(let point of path){
    console.log(point); // [1, 1], [1, 2], [2, 3]
}

BigInt
BigInt是JavaScript新的原始类型,可以精确表示⼤于2^53-1的整数。

BigInt字⾯量:数字+n
BigInt运算:不能与Number直接进⾏运算
显式类型转换:BigInt与Number相互转换,这样才能进行运算

const a = Number.MAX_SAFE_INTEGER + 1;
const b = a + 1;
const c = BigInt(a) + 1n;
console.log(a, b, c);
//9007199254740992
//9007199254740992
//9007199254740993n
const d = Number.MAX_VALUE;
const e = d * 2;
const f = BigInt(d) * 2n;
console.log(d, e, f);
//1.7976931348623157e+308
//Infinity
//3595386...9716736n

BigInt 内置⽅法
BigInt提供了两个内置⽅法可以转换为width位的有符号或⽆符号BigInt值。

BigInt以计算机补码表示负数
BigInt.asIntN转换为对应的有符号整数
BigInt.asUintN转换为对应的⽆符号整数

const a = -1n;
const b = BigInt.asUintN(16, a);//16位的无符号整数
console.log(b); //65535n
const c = BigInt.asIntN(16, b - 1n);//16位的有符号整数
console.log(c); // -2n

4 JS函数
非原始类型:对象,在此之前要先介绍函数,对于JS来说函数很重要
4.1 函数声明:可以通过function关键字来声明⼀个函数
function:通过function关键字可以声明⼀个函数。
函数声明没有块级作⽤域,var也是没有块级作用域
函数声明会被提升(hoist)

//不带参数的函数
function foo() {
    console.log('foo bar');
}
//带有两个参数的函数,y的默认值是0
function add(x, y = 0) {
    return x + y;
}
console.log(add(1, 2)); //3,console放到function前面也可以
//可选参数,sum最少有一个数,最多有任意多的数字
function sum(x = 0, ...rest) {
    return rest.reduce((a, b) => a + b, x);
}
console.log(sum(1, 2, 3, 4, 5)); //15
//解构参数,计算向量常数
function vectorLength({x = 0, y = 0, z = 0} = {}) {
    return Math.hypot(x, y, z);
}
const v = {x: 3, y: 4, z: 0};
console.log(vectorLength(v)); //5

4.2 函数表达式:函数也是对象,因此我们可以把⼀个函数赋给⼀个变量
可以将函数作为对象使⽤。

函数表达式不会被提升(hoist)
函数表达式可以具名也可以匿名
函数表达式具名只能在内部访问

const factorial = function f(n) {
    if(typeof n == ' number') n = BigInt(n);
    if(n <= n) return 1n;
    return n * f(n - 1n);
}//递归函数,在外面必须用factorial,不能用f
console.log(factorial(100));//这里console写到前面就会报错
//933262154...00000000000000n
const factorial = function f(n) {
    if(typeof n == ' number') n = BigInt(n);
    if(n <= n) return 1n;
    return n * factorial(n - 1n);
}
console.log(factorial(100));
//933262154...00000000000000n
const angles = [30, 45, 60, 90, 180];
const radAngles = angles.map(
    function(angle) {
        return Math.PI * angle / 180
    });//角度转换为弧度
console.log(radAngles);

4.3 箭头函数:匿名函数表达式还可以写为箭头函数
函数表达式简写
单⾏的箭头函数可以省略花括号和return
只有⼀个参数的箭头函数可以省略圆括号
箭头函数不能具名,也没有this上下⽂(下节课详细说)

const angles = [30, 45, 60, 90, 180];
const radAngles = angles.map(
    angle  → Math.PI * angle / 180
);
console.log(radAngles);//对比上面的角度转弧度

const arr = [1, 2, 3, 4, 5];
const sum = arr.reduce((x, y) => x + y);
console.log(sum); //15

执⾏上下⽂(闭包)(高级部分)
函数有执⾏上下⽂,运⾏时会产⽣“闭包”。

闭包是运⾏时由函数调⽤产⽣的
通过闭包可访问执⾏上下⽂中的数据,块级、函数
如果产⽣闭包的引⽤被销毁,闭包被销毁

function sayHelloTo(person) {
    return function() {
        console.log(`Hello ${person}!`);
    }
} //函数返回到另一个函数:高阶函数
let greeting1 = sayHelloTo('Tom');
let greeting2 = sayHelloTo('Jerry');//产生过程中闭包存在,环境未销毁
greeting1(); // Hello Tom!
greeting2(); // Hello Jerry!
greeting1 = null;
greeting2 = null;//销毁闭包

4.4 this上下⽂:函数的this上下⽂,指函数的实际调⽤者对象
在函数体内部,可以通过this对象访问函数调⽤this上下⽂。所谓函数调⽤的this上下⽂,是指函数的实际调⽤者对象

const person = {
    firstName: '三',
    lastName: '张',
    getFullName: function() {
        return this.lastName + ' ' + this.firstName;//this指的是person
    },//一个函数
};

console.log(person.firstName); //三
console.log(person.lastName); //张
console.log(person.getFullName()); //张三

person.sayHelloTo = function() {
    console.log(`Hello ${this.lastName}!`);
}//这里的this就不是person,全局里是global对象
person.sayHelloTo(); //Hello 张!
setTimeout(person.sayHelloTo, 100);
//Hello undefined!

箭头函数
箭头函数没有this上下⽂,利⽤这⼀特性,可以让箭头函数访问外部作⽤域的this上下⽂。

const person = {
    firstName: '三',
    lastName: '张',
    getFullName() {
        return this.lastName + ' ' + this.firstName;
    },
    sayHelloTo() {
        setTimeout(
            ()  => console.log(`Hello ${this.lastName}!`)
        , 100);//这里可以使用,访问外部作用域下的this
    }
};

person.sayHelloTo(); // Hello 张!

4.5 动态绑定:函数的this上下⽂可以在调⽤时指定和动态改变。
this上下⽂在函数调⽤时可以动态指定,⽅法是通过函数的call、apply或bind⽅法来调⽤.

function getFullName() {
    return this.firstName + ' ' + this.lastName;
} 
const person1 = {firstName: '三', lastName: '张'};
console.log(getFullName.call(person1)); // 三 张

const logger = {
    type: 'info',
    count: 0,
    log: function(message) {
        console[this.type](message, ++this.count);
    },
};

// call、apply、bind 不光能绑定this上下⽂,还能绑定部分参数
setInterval(
    logger.log.bind(logger, 'heart beat')
    , 1000);//每1000毫秒调用一次

4.6 Function类:函数对象是Function类的实例,可以通过Function类动态创建
函数是Function类的实例。我们可以⽤Function类动态创建函数对象。一般业务不常用

const add = new Function('x', 'y', 'return x + y')
console.log(add(1, 2)) // 3
const tpl = "{foo: 'bar'}"
// tpl不是规范的JSON字符串,无法使⽤JSON.parse解析
const obj = (new Function(`return ${tpl}`))()
console.log(obj) // {foo: 'bar'}

5 JS对象
5.1 构造:对象构造可以⽤字⾯量,也可以⽤构造器,或者⽤原型。
对象是最基础的复合类型,它是⼀组属性的集合。对象有⼏种构造⽅式。

//字⾯量
{
    let myObj = {
        name: "akira",
        birthday: "12-29"
    };
} 
//构造器
{
    let myObj = new Object();
    myObj.name = 'akira';
    myObj.birthday = '12-29';
} 
//原型
{
    let myObj = Object.create({//这里是一个原型
        name: "akira",
        birthday: "21-29"
    });
}

构造器与类
JavaScript是⾯向对象的编程语⾔,对象默认的构造器是Object,我们可以定义函数作为构造器或者定义class

//函数作为构造器
{
    function Vector2D(x, y) {
        this.x = x;
        this.y = y;
    }
    const v = new Vector2D(3, 4);//v构造出一个实例
    console.log(v);
} 
//定义class
{
    class Vector2D {
        constructor(x, y) {//构造函数
            this.x = x;
            this.y = y;
        }
    }
    const v = new Vector2D(3, 4);//也可以得到一个实例
    console.log(v);
}

5.2 属性:对象的属性名可以是字符串或Symbol
5.2.1 属性定义
对象属性定义的key可以⽤合法标志符、字符串或⽅括号[ ]中的表达式。如果是表达式,计算出的值应该是字符串或者Symbol。

const data1 = {
    foo: 'foo',
    bar: 'bar',
};
const data2 = {
    'foo-bar': 'foo bar',//带-是不合法的
};
const foo = 'foo';
const data3 = {
    [foo + 'bar']: 'foobar',//利用表达式
};
console.log(data1); // {foo: 'foo', bar: 'bar'}
console.log(data2); // {'foo-bar': 'foo bar'}
console.log(data3); // {foobar: 'foobar'}

const id = Symbol('id');
const obj = {[id]: 'message'};
console.log(obj); // {Symbol(id): "message"}

5.2.2 属性访问
对对象指定属性的访问,有两种⽅式,如果属性名是合法的标识符,那么可以使⽤ . 操作符访问。如果属性名不是合法标识符,⽐如不符合标识符命名的字符串或Symbol,或者属性名需要经过计算,那么我们可以通过[ ] 操作符访问

const id = Symbol('id');
const data = {
    'foo-bar': 'fooЉbar',
    [id]: 'message',//方括号访问
    '12': 'result',
};
console.log(data['foo-bar'], data[id], data[3 * 4])//表达式也可以访问
// foo-bar message result

// []允许我们动态计算表达式的值作为属性名
// 这对于我们动态转换数据结构很有帮助。
const students = ['张三', '李四', '王五'];
const scores = [70, 100, 80];
const results = {};

for(let i = 0; i < students.length; i++){
    results[students[i]] = scores[i];//对应的映射
}

5.2.3 属性遍历
除了通过对象属性名(key)访问对应的值,我们可以对属性的key或value分别进⾏遍历。
⽤ for…in 遍历
⽤ for…of 遍历

const scores = {
    '张三': 80,
    '李四': 100,
    '王五': 60,
};
for(let name in scores) {//遍历出所有可枚举属性,早期版本
    console.log(name, scores[name]);
    //分别输出 张三,80 李四,100 王五,60
} 
const scores = {
    '张三': 80,
    '李四': 100,
    '王五': 60,
};
for(let [name, score] of Object.entries(scores)) {//可以不遍历不想要的属性
    console.log(name, score);
    //分别输出 张三,80 李四,100 王五,60
}

5.2.4 增删改
对象属性可以动态添加,直接给对象属性赋值就可以,要将已有属性删除,可以⽤delete操作,通过in操作符,可以判断⼀个属性是否在对象上存在.

const data = {};
//添加属性
data.foo = 'foo';
data['foo-bar'] = 'foo-bar';
delete data.foo; // 删除属性
console.log(data.foo, data['foo-bar']);
//undefined foo-bar
console.log('foo' in data, 'foo-bar' in data);
// false true

5.3 原型:对象原型能在实例中共享数据
原型是对象上的特殊的属性,对象可以通过原型来共享数据。
通过Object.create共享
通过构造器共享

//通过Object.create共享
{
    const o = {foo: 'foo'};
    const a = Object.create(o);//以o为原型共享出来
    const b = Object.create(o);
    a.bar = 'bar1';
    b.bar = 'bar2';
    console.log(a.foo, a.bar); // foo, bar1
    console.log(b.foo, b.bar); // foo, bar2
}

//通过构造器的prototype属性访问原型,更常用一些
{
    function Foo(message = 'foo') {
        this.foo = message;
    }
    Foo.prototype.bar = 'bar';
    const a = new Foo('foo1'),
    b = new Foo('foo2');
    console.log(a.foo, a.bar); // foo1, bar
    console.log(b.foo, b.bar); // foo2, bar
}

原型链
在JavaScript中,⼏乎所有对象都可以访问它的构造器上的原型对象,⽽这个原型对象本身⼜可以访问它⾃身的构造器上的原型对象,依次类推,就构成⼀个链式结构,被称为原型链。(可以实现类的继承)
在这里插入图片描述

function Animal() {}
Animal.prototype = {
    eat() {console.log("I'm eating")}
}
function Person() {}
Person.prototype = new Animal()

Person.prototype.speak = function() {
    console.log('I can say something')
}

function Student(name) {
    this.name = name
}
Student.prototype = new Person()
Student.prototype.study = function() {
    console.log("I'm learning...")
} 

const student = new Student('张三')
console.log(student.eat(),
    student.speak(), student.study())

类继承
原型链在JavaScript中起到了类继承的作⽤,⽽ES6之后,我们也可以直接⽤class的语法来实现类继承

class Animal {
    eat() {
        console.log("I'm eating")
    }
} 
class Person extends Animal {
    speak() {
        console.log("I can say something")
    }
} 
class Student extends Person {
    study() {
        console.log("I'm learning...")
    }
} 
const student = new Student('张三')
console.log(student.eat(), student.speak(),
    student.study())

5.4 访问器:根据对象的其他属性或者别的数据的改变⽽改变的⼀类属性
访问器属性
对象可定义访问器属性(Accessor Property)。

使⽤get/set定义
使⽤defineProperty/defineProperties定义

class Vector2D {
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }//二维向量
    get length() {
        return Math.sqrt(this.x ** 2 + this.y ** 2);
    }//定义长度
    set length(len) {
        const scale = len / this.length;
        this.x *= scale;
        this.y *= scale;
    }//把相关属性定义出来
} 
const v1 = new Vector2D(3, 4);
console.log(v1.x, v1.y, v1.length); // 3, 4, 5

v1.length *= 2;
console.log(v1.x, v1.y, v1.length); // 6, 8, 10

5.5 描述符:是⼀类特殊的对象,⽤来描述属性的访问特性
属性描述符
通过defineProperty/defineProperties定义属性可以指定属性描述符。可以动态添加和删除属性。只读属性是只用get或set。
下面图中:前三个是定义行为的,可以控制true和false,就可以定义是否可删除或可写或可枚举(影响for…in行为)。value是普通属性,不能与get或set同时存在
在这里插入图片描述
defineProperties
通过defineProperties和属性描述符定义属性。

动态添加在对象上的普通属性默认是可删除、可写、可枚举的。
定义在class上的普通属性默认是可删除、可写、不可枚举的。不会被for…in出来
可以使⽤Object.getOwnPropertyDescriptors来获取属性描述符。

const obj = {};
Object.defineProperties(obj, {//定义属性
    foo: {
        value: 'foo',
        writable: true,
        configurable: true,//默认情况下都是不可写不可删除
    },
    bar: {
        get() {return 'bar'},//不可写也不可删除
    }
});
console.log(obj.foo, obj.bar); // foo bar
obj.foo = 'foo2';
console.log(obj.foo, obj.bar); // foo2 bar
delete obj.foo;
console.log(obj.foo, obj.bar); // undefined bar

5.6 解构:对象的初始化和赋值是可以解构
我们介绍过函数的解构参数,除了传参之外,对象的初始化和赋值也是可以解构的。

可以⽤属性名来解构
也可以在解构的同时重新命名
解构可以嵌套
除了⼀般对象,数组也可以解构

{
    const obj = {foo: 'foo', bar: 'bar'};
    const {foo, bar} = obj;
    console.log(foo, bar); // foo, bar
}
{
    const obj = {foo: 'foo', bar: 'bar'};
    const {foo: a, bar: b} = obj;
    console.log(a, b); // foo, bar
}
{
    const obj = {x: {y: 1}, z: 2};
    const {x: {y}, z} = obj;
    console.log(y, z); // 1, 2
}//嵌套解构
{
    const obj = {foo: 1, bar: 2};
    for(let [key, value] of Object.entries(obj)) {
        console.log(key, value);
    } // 分别打印 foo,1 和 bar,2
}//数组做解构

JavaScript 内置类型
重要的为打钩的
regex正则表达式
在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值