总结
ES5
ES5
严格模式
-
严格模式
定义:'use strict'
范围:
全局:script标签中的第一句话
函数:任何一个函数中的第一句话后面的语句都默认在严格模式下
带来了什么
作用
:对象中不允许出现重名的属性
函数中的形式参数不允许重名
变量不允许重名
不允许自定义函数中的this指向window,得到结果undefined
eval() 传入一个字符串 可以针对于JS语句进行解析
Object下的一些方法
- Object下的一些方法
-
create方法
: 以某一个对象为显式原型属性基础之上建立的新对象let newObj = Object.create(obj,{ 属性名:{ value:100 } }) newObj.属性名 = 新值;
-
defineProperty: Object.defineProperty(哪一个对象,'属性名',{})
-
defineProperties:Object.defineProperty(哪一个对象,{ 属性名:{ value:100 ... } })
这两个方法都是针对于已有对象来添加属性
关于this指向
- 关于this指向:
默认情况下任何一个函数之中都有this指向
修改:call、apply、bind
call和apply:主要在于函数中传递实际参数的语法格式不同
apply接收参数一定为数组
哪一个函数.call(要修改的新的this指向的对象,实际参数1,实际参数2....)
哪一个函数.apply(要修改的新的this指向的对象,[实际参数1,实际参数2....])
bind如果有参数的情况下:
哪一个函数.bind(要修改的那个this指向,实际参数1,实际参数2…)
或者
哪一个函数.bind(要修改的那个this指向)(实际参数1,实际参数2…)
call和bind:
是否立即执行:call修改完this指向,函数将立即执行,bind不会立即执行(后期)
是否有返回值:call没有,bind返回的是与原函数一模一样的新函数对象,又因为bind不会立 即执行,
所以需要手动调用,当然调用的也就是新函数对象
ES6:
ES6:
关键字
- 关键字
-
1.1 let
let 变量 = 值;
let 变量名1 = 值, 变量名2 = 值…
let 变量;影响:
- 变量不允许重名 let a = 1; let a = 2;
- 作用域属于块级
- 不存在变量提升
- 不影响作用域链
function f1(){ let a = 1; function f2(){ //let a = 2; console.log(a) } f2(); } f1();
-
1.2 const
const 常量名 = 值;
const 常量名1 = 值, 常量名2 = 值…影响:
- 变量不允许重名 let a = 1; let a = 2;
- 作用域属于块级
- 不存在变量提升
- 不影响作用域链
- const常量值(基础值/地址值)是不允许后期更改
解构赋值 [非常重要]~!
-
解构赋值 [非常重要]~!
数组和对象存在解构赋值数组:完全解构
let [变量1,变量2,变量3] = [1,2,3]部分解构
let [,a,] = [1,2,3]对象:完全解构
let {a,b,c} = {a:100,b:200};部分解构
let {a} = {a:100,b:200}
模板字符串
- 模板字符串
- 3.1 定义 let 变量 = ``;
- 3.2 使用
- 3.2.1 可以获取字符串对象中的属性/方法 (正常字符串的使用)
- 3.2.2 可以换行
- 3.2.3 可以直接在字符串中解析值(变量(基本数据类型/引用数据类型))
let 变量 =${arr[0]}
let 变量 =${obj.属性名}
let 变量 =${a}
对象的简化方式
- 对象的简化方式
- 4.1 属性的简化:对象中的属性名和外部的变量名一致,且变量名是作为属性值使用的时候,可以简写一个词
- 4.2 方法的简化:省略了冒号和function
方法名(){}
箭头函数
- 箭头函数
-
5.1 定义:
let 变量 = ()=>{}
方法(()=>{}) -
5.2 带参数
只有一个参数,可以省略小括号:let 变量 = 参数名 =>{} -
5.3 带返回值
如果{}中只有一句话,则可以省略{}
let 变量 = ()=>console.log(xxx)如果{}中的语句恰好是箭头函数中的返回值,那么可以省略return
let 变量 = ()=>值; -
5.4 注意事项:
- 不能被作为构造函数使用
2)箭头函数没有自己的this指向,this指向取决于当前函数声明所在的环境(全局环境–window下;函数环境—局部) - 没有arguments,后期可以被rest参数替代
- 不能被作为构造函数使用
参数默认值
- 参数默认值
ES5
function fun(a,b = 值,c = 值){
let a = 1;
let b = 2;
let c;
}
fun(1,2)
ES6
let fn = (a,b = 值,c = 值)=>{
}
fn(1,2,3)
当如果对应实际参数没有设置,且也没有设置默认值,相当于只是创建了局部变量(undefined)
当如果对应实际参数没有设置,但是设置了默认值,那么形参的内容就按照默认值来
当如果对应实际参数有设置,也设置默认值,则按照实际参数来
参数中完成解构[重要!!]
数组
function f1([a,b,c]){
}
f1([1,2,3])
对象
function f2({x,y = 值}){
}
f1({x:100,y:200})
rest参数
- rest参数
主要和ES5中的arguments功能类似,当函数传入多少个实际参数的数量不确定的时候,可能会用到rest参数
rest参数返回的是一个纯数组,
arguments是一个伪数组,如果想要转换成纯数组,可以使用Array.from(arguments)
定义:let fn = (...args)=>{} fn(1,2,3,4)
扩展运算符(展开运算符)
- 扩展运算符(展开运算符)
语法:...数组/对象
展开的是数组/对象的键值中的值(基本值/引用值)
使用:
数组:
对象:let arr = ['a','b','c',{a:1}] let arr1 = [...arr];
let obj = {x:100,y:200} let obj1 = {...obj}
Symbol
- Symbol
- 9.1 定义:
let 变量 = Symbol()
- 9.2 比较:相同内容的Symbol之间比较一定为false,因为Symbol主要是针对于对象中唯一属性名的标识
- 9.3 使用:
let 变量 = Symbol('值');
let obj = {
[变量]:值(大概率是方法的偏多)
}
或者
let obj = {}
obj[变量] = 值;
- 9.1 定义:
iterator迭代器
- iterator迭代器
- 10.1 哪些数据有迭代器:数组、arguments、set、map、字符串、nodeList、HTMLCollection…
- 10.2 有迭代器证明可以使用for of循环
for(let 变量 of 数据){}
- 10.3 for of和for in之间的区别:
- for of Es6中提出的
- for of中的变量存储的值无非下标,for in中的变量存储的就是下标
- for of中如果想要获取下标需要借助于数组中的entries方法
set集合和map集合
- set集合和map集合
-
11.1 set集合
-
怎么定义:
let 变量 = new Set()
参数不是必要的参数,但是传递的参数接收的是一个一维数组 -
实际作用:
- 排重
let 变量 = new Set([1,3,4,5,6,7,4,2,1,2,3,2])
- 交集(两个集合中相同的部分)
let arr = [1,2,3,4,5,3,5,2,2,4,2] let arr1 = [2,3,4,5,6,2,4,2,4,1,1] let s1 = new Set(arr) let s2 = new Set(arr1); [...s1].filter(item=>s2.has(item))
- 并集(去重后的两个集合放到一起)
let arr = [1,2,3,4,5,3,5,2,2,4,2] let arr1 = [2,3,4,5,6,2,4,2,4,1,1] let s1 = [...new Set(arr)] let s2 = [...new Set(arr1)]; let arr2 = [...new Set([...s1,...s2])];
- 差集(相对每一个集合中不同的部分)
let arr = [1,2,3,4,5,3,5,2,2,4,2] let arr1 = [2,3,4,5,6,2,4,2,4,1,1] let s1 = new Set(arr) let s2 = new Set(arr1); [...s1].filter(item=>!s2.has(item))
- 排重
-
方法的使用:
- 添加:
s1.add(值)
- 删除:
s1.delete(值)
- 是否存在:
s1.has(值)
- 清除:
s1.clear()
- 长度:
s1.size
- 遍历:
for of
- 添加:
-
-
11.2 map集合
-
- 怎么定义:
let 变量 = new Map()
参数不是必要的参数,但是传递的参数接收的是一个二维数组 - 实际作用:
let m1 = [...new Map([['name', '张三'], ['age', 23], ['sex', '男']])]; for (let i = 0; i < m1.length; i++) { for (let j = 0; j < m1[i].length; j++) { console.log(m1[i][j]) } }
- 怎么定义:
类和对象
- 类和对象
类:其实更像是一个模板,描述特定类型对象的一个模板
对象:由这个模板所生产出来的一个实实在在的一个例子,可以操作的
** 定义类**
class 类名{
- 属性的定义
- 12.1 直接赋值
属性名 = 属性值(实例化对象身上) [适合于所有实例化对象针对于该属性值是一致的时候]
- 12.2 通过构造器的方式
[适合于所有实例化对象的这些属性的值是不一致的,需要通过调用类的构造函数进行传递实际参数]
constructor(变量名){
this.属性名 = 变量名
this.方法名 = function(){}
}
- 方法
- 12.1 直接赋值
方法名 = function(){}(实例化对象身上)
//存入类的原型
方法名(){}
//在类对象本身身上添加属性/方法
static 属性名 = 属性值;
static 方法名 = function(){}
//getter和setter
set 属性名(value){
this.别名=value;
}
get 属性名(){
return this.别名;
}
}
按照这个模板来实例化对象
let 变量 = new 类(); // {}
变量.属性名 = 新值;
-> 类的继承
-> 定义:class 子类 extends 父类{}
-> 继承:子类将父类中的所有的部分全部继承到子类
-> class Person{
constructor(name,age,sex){
this.name = name;
this.age = age;
this.sex = sex;
}
方法名(){}
}
-> class Student extends Person{
constructor(high){
super();
this.high = high;
}
}
let s1 = new Student(180); // 0x100
console.log(s1.__proto__ = Student.prototype); //均指向一个Object类型的实例化对象 0x200
console.log(Student.prototype.__proto__ = Person.prototype)
console.log(Person.prototype.__proto__ = Object.prototype)
console.log(s1.方法名())
ECMAScript5语法扩展
一、严格模式
1.1 介绍
ES5 除了正常运行模式,还添加了第二种运行模式:“严格模式”(strict mode)。
严格模式顾名思义,就是使 JavaScript 在更严格的语法条件下运行。
1.2 作用
- 消除 JavaScript 语法的一些不合理、不严谨之处,减少一些怪异行为
- 消除代码运行的一些不安全之处,保证代码运行的安全
- 为未来新版本的 JavaScript 做好铺垫
1.3 使用
在全局或函数的第一条语句定义为: 'use strict'
如果浏览器不支持,只解析为一条简单的语句, 没有任何副作用
// 全局使用严格模式
'use strict';
girl = '大沫沫';
// 函数中使用严格模式
function main(){
'use strict';
girl = '大沫沫';
}
main();
1.4 语法和行为改变
- 必须用 var 声明变量,不允许使用未声明的变量
- 禁止自定义的函数中的 this 指向 window
- 创建 eval 作用域(eval 是一个函数, 接受字符串参数, 会对字符串进行 JS 语法解析并运行)
- 对象不能有重名的属性(Chrome 已经修复了这个 Bug,IE 出现报错)
- 函数不能有重复的形参
- 新增一些保留字, 如: implements interface private protected public
二、Object 扩展方法
2.1 create方法
语法:Object.create(prototype, [descriptors])
Object.create() 详解
Object.create()MDN详解
Object.create 方法可以以指定对象
为原型(prototype)
创建新的对象
,
同时可以为新的对象设置属性, 并对属性进行描述,但是要注意对象的键值必须是一个对象
已有属性可以随意修改,但是新增的属性必须要通过设置
才能有权限
操作
如果要是没有以哪一个对象为原型,则可以第一个参数设置为null
- value : 指定值
- writable : 标识当前属性值是否是可修改的, 默认为 false
- configurable:标识当前属性是否可以被删除 默认为 false
- enumerable:标识当前属性是否能用for in 枚举 默认为 false
- get: 当获取当前属性时的回调函数
- set: 当设置当前属性时
//创建一个汽车的对象
var car = {
name : '汽车',
run: function(){
console.log('我可以行驶!!');
}
};
//以 car 为原型对象创建新对象
var newObj = Object.create(car, {
brand: {
value: '奥迪',
writable: true, //是否可修改
configurable: true, //是否可以删除
enumerable: true //是否可以使用 for...in 遍历
},
color: {
value : '黑色',
wriable: false,
configurable: false,
enumerable: true
},
price:{
//如果要是已经使用了getter和setter这种方式,则配置权限属性只能有可以删除和可以枚举两个
get:function(){
return this.p;
},
set:function(value){
this.p = value;
},
configurable: true,
enumerable: true
}
});
console.log(newObj);
2.2 defineProperties
语法:Object.defineProperties(object, descriptors)
直接在一个对象
上定义新的属性
或修改现有属性
,并返回该对象
。
-
object 要操作的对象
-
descriptors 属性描述
-
get 作为该属性的 getter 函数,如果没有 getter 则为undefined。
函数返回值将被用作属性的值。
-
set 作为属性的 setter 函数,如果没有 setter 则为undefined。
函数将仅接受参数赋值给该属性的新值。
-
// 定义对象
var star = {
firstName: '刘',
lastName : '德华'
};
// 为 star 定义额外的属性
Object.defineProperties(star, {
fullName: {
get: function(){
return this.firstName + this.lastName;
},
set: function(name){
var res = name.split('-');
this.firstName = res[0];
this.lastName = res[1];
}
}
});
// 修改 fullName 属性值
star.fullName = '张-学友';
// 打印属性
console.log(star.fullName);
扩展:
Object.defineProperty()
方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性.
并返回此对象,该方法也是vue实现数据绑定和更新数据的核心代码之一。
区别:defineProperty设置属性需要一个一个的设置,而defineProperties可以批量设置多个属性
语法:Object.defineProperty(object, prop,descriptor)
- obj:要定义属性的对象;
- prop:要定义或修改的属性名称,
注意属性必须写成字符串
- descriptor:要定义或修改的属性描述符;
描述对象中的属性及其各自的作用:
属性名 | 作用 |
---|---|
configurable | 标识当前属性是否可以被删除 默认为 false |
enumerable | 标识当前属性是否能用for in 枚举 默认为 false |
writable | 标识当前属性值是否是可修改的, 默认为 false |
get | 获取该属性值时调用该函数 |
set | 设置该属性值时调用该函数 |
value | 该属性的值或者方法 |
注意:value属性不能与set和get属性同时使用;writable属性不能与set和get属性同时使用
var obj = {};
//直接在一个对象上操作属性(新增、删除、修改)
Object.defineProperty(obj, 'name', {
value: '张三',
writable: true
});
Object.defineProperty(obj, 'age', {
get: function () {
return this.a
},
set: function (value) {
this.a = value;
}
});
Object.defineProperty(obj, 'height', {
configurable: true,
writable: false,
enumerable: true,
value: 180,
})
obj.name = '李四';
obj.age = 18;
obj.height = 190;
console.log(obj.height);
console.log(obj.age);
console.log(obj);
练习:要求添加 total 属性, 获得班级的总分数
//要求添加 total 属性, 获得班级的总分数
var banji = {
name: 'HTML',
scores: [
{
name: '张三',
score: 90
},
{
name: '李四',
score: 85
},
{
name: '王五',
score: 95
},
{
name: '赵六',
score: 88
}
]
};
var newObj = Object.defineProperties(banji,{
total:{
get:function(){
//定义一个总和变量
var sum = 0;
//遍历数组
for(var i = 0 ; i < banji.scores.length;i++){
sum+=banji.scores[i].score;
}
return sum;
}
}
});
console.log(newObj.total);
三、call、apply、bind
- call 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数
- apply 方法调用一个具有给定 this 值的函数,以及作为一个数组(或类似数组对象)提供的参数
- bind 同 call 相似,不过该方法会返回一个新的函数,而不会立即执行
修改this指向:call、apply、bind
哪一个函数.call(要修改的那个this指向,实际参数1,实际参数2…);
哪一个函数.apply(要修改的那个this指向,[实际参数1,实际参数2…]);
call和apply这两个方法修改完this指向之后,函数将立即执行
bind方法先修改this指向,但是返回了一个与原函数结构一模一样的新函数 ,只不过this指向是新的而已
如果想要让这个函数执行,需要程序员手动调用(新函数)
如果有参数的情况下:
哪一个函数.bind(要修改的那个this指向,实际参数1,实际参数2..)
或者
哪一个函数.bind(要修改的那个this指向)(实际参数1,实际参数2..)
//call apply bind 都是用来改变this的指向
function test(a, b, c) {
console.log(this)
console.log(a, b, c)
}
// test(1,2,3);
// call() 参数1:修改后的this, 从参数2开始是实参的列表
test.call({},1,2,3)
// apply() 参数1:修改后的this, 参数2是数组,数组中的元素是实参列表
test.apply({name:'张三'},[1,2,3])
//bind 参数是修改后的this, bind方法执行完返回的是一个与test结构一样的新的函数
// 该函数调用需要传递实参的列表
// var f = test.bind({})
// f(4,5,6)
// console.log(f)//函数
// console.log(f === test)//false
//简写方式
test.bind({})(7, 8, 9)
ECMAScript6语法
一、关键字
let
语法
单变量:let 变量名 = 值;
多变量:let 变量1= 值,变量2=值…,变量n;
//声明一个变量
let a;
console.log(a); //undefined
//定义单变量
let name = "阿香";
console.log(name); //阿香
//定义多变量
let star = "赵丽颖" , age = 33 , arr = [];
console.log(star,age,arr); //赵丽颖 33 []
注意事项:
- 变量不能重复声明
例如:
let num = '34';
let num = 56;
console.log(num); //Identifier 'star' has already been declared
-
let声明的变量属于块级作用域
常见的块级作用域:if、else、while、for…
例如:
if(true){
let star = "天蝎座";
}
console.log(star); //star is not a defined
对比var:
if(true){
var star = "天蝎座";
}
console.log(star); //天蝎座
- let声明的变量不存在变量提升
【其实有一些文献中说let存在变量提升,只不过会将变量放在一个暂时性死区中,所以我们无法获取。
所以我们可以直接理解成let声明的变量不存在变量提升】
对比var
console.log(star); //undefined
var star = '天蝎座'
对比let
console.log(star); //报错 Cannot access 'star' before initialization
let star = '天蝎座'
- 不影响作用域链
function test(){
let star = '天蝎座';
function fun(){
console.log(star); //天蝎座
}
fun();
}
test();
const
语法
单常量:const 常量名 = 值;
多常量:const 常量1= 值,常量2=值…,常量n;
注意事项:
- 常量在声明的时候,一定要赋初始值
例如:
const A; //报错 Missing initializer in const declaration
- 常量在声明的时候,一般使用全大写的形式(约定)
例如:
const NAME = 'admin';
- 常量的值,不允许被修改
例如:
const NAME = 'admin';
NAME = 'admin'; //报错 Assignment to constant variable
console.log(NAME);
- 常量和let变量一样,都拥有块级作用域
例如:
if(true){
const STAR = '天蝎座';
console.log(STAR);
}
console.log(STAR);
- 对于数组和对象元素的修改,不算是对常量的修改,因为数组和对象使用的是地址
const SNAME = ['张三','李四','王二麻子'];
//直接修改不允许
// SNAME = 100;
//但是可以修改数组中的元素
SNAME[0] = '王五';
console.log(SNAME);
const obj = {
name:'于谦',
age:40
}
obj.age = 50;
console.log(obj);
二、变量解构赋值
ES6允许按照一定的模式从数组
和对象
中提取值
,对变量进行逐一赋值,这种过程就被称作解构赋值。
2.1 数组中
语法:let [变量1,变量2…] = 数组
例如:
let arr = ['郭德纲','岳云鹏','老秦','孙越'];
//之前的数组取值:靠下标
console.log(arr[0]); //郭德纲
console.log(arr[1]); //岳云鹏
console.log(arr[2]); //老秦
console.log(arr[3]); //孙越
console.log("=====================");
//现在就可以利用数组的解构赋值
let [guo,yue,qin,sun] = arr;
console.log(guo);//郭德纲
console.log(yue);//岳云鹏
console.log(qin);//老秦
console.log(sun);//孙越
//这是数组解构最基本类型的用法,还可以解构对象数组
let [a, b, c] = [{name: '老王'}, {name: '老李'}, {name: '老刘'}];
console.log(a, b, c); // {name: '老王'}, {name: '老李'}, {name: '老刘'}
//数组模式和赋值模式统一:等号左边和等号右边的形式要统一,如果不统一解构将失败。多维数组解构
let [a, [b, c], d] = [1, [2, 3], 4];
console.log(a, b, c, d); // 1 2 3 4
//提取除第二、三个外的所有数值
let [a, , , d] = [1, 2, 3, 4];
console.log(a, d); //1 4
// 解构中默认值的设置
let [a,b = '我是默认值'] = [1,2]
console.log(a,b);//1,2
//惰性赋值 只有在解构失败时才会允许默认值表达式
let [a,b = '我是默认值'] = [1]
console.log(a,b);//1,我是默认值
2.2 对象中
对象的解构与数组有一个重要的不同。
数组的元素是按次序排列的,变量的取值由它的位置决定;
对象的属性没有次序,变量必须与属性同名,才能取到正确的值。
对象解构赋值的内部机制:是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者
语法:let {变量1,变量2…} = 对象
例如:
//对象
let obj = {
name:"张三",
age:28,
song:function(){
console.log('唱');
},
dance:function(){
console.log('跳');
}
}
//之前获取对象属性值的方式
console.log(obj.name); //张三
console.log(obj.age); //28
console.log(obj.song); //f(){}
console.log(obj.dance);//f(){}
//现在可以利用对象的解构赋值的方式
let {name,age,song,dance,height,sex = '男'} = obj
console.log(name);//张三
console.log(age);//28
console.log(song);//f(){}
console.log(dance);//f(){}
console.log(height);//解构失败 返回undefined
console.log(sex);//设置默认值 男
//当然并非对象中有多少键值对都需要结构成对应的变量,需要什么解什么
console.log(name);//张三
console.log(song);//f(){}
//当然后期会碰到相对复杂一些的对象结构
let obj1 = {
name1: "赵丽颖",
age: 33,
works: ['知否', '花千骨', '杉杉来了', '楚乔传'],
hobby: {
eat: '吃饭',
sleep: '睡觉'
}
}
let { name1, age, works: [a], hobby: { eat } } = obj1;
console.log(name1); //赵丽颖
console.log(a); //知否
console.log(eat); //吃饭
三、模板字符串
ES6中引入新的一种声明字符串的方式
-
语法:
字符串
let str = `我是一种新字符串的定义方式`; console.log(str);
-
好处:
-
内容中可以直接出现换行符
let str = ` <ul> <li>张三</li> <li>李四</li> </ul>` console.log(str);
-
变量间的拼接
let str = '王五'; let str1 = `${str}是一个好人`; console.log(str1);
-
四、对象简化方式
对象中可以简写属性和方法
当属性名和属性值同名时,可以省略
对象中的函数可以省略function
<body>
<script>
let name = "张三";
let age = 23;
let getName = function(){
console.log('获取姓名');
}
//声明一个对象
let obj = {
// name:"张三",
// age:23,
//当属性名和属性值同名时,可以省略
name,
age,
getName,
/* change:function(){
console.log('希望通过学习,改变自己');
} */
change(){
console.log('希望通过学习,改变自己');
}
}
console.log(obj); //{name: '张三', age: 23, getName: ƒ, change: ƒ}
</script>
</body>
五、箭头函数
在ES6中,新增一种新的函数定义方式(箭头函数)
5.1 声明语法
ES5语法
//声明函数
let fn = function(形参1,形参2...){
return 返回值;
}
//调用函数
fn(100,200);
ES6语法
//声明函数
let fn = (形参1,形参2...)=>{
return 返回值;
}
//调用函数
fn(100,200);
5.2 this指向
箭头函数中的this是静态this,不会随着函数的环境变化而变化;
始终指向函数声明时所在作用域下的this的值,且无法修改this指向
箭头函数中的this与函数前面的调用者无关,与函数声明所在的环境中的this指向有关。
箭头函数本身是没有自己的this指向,其this完全听函数所在的环境(函数内/全局内)中的this
ES5语法
function getName() {
console.log(this.name);//王牌对王牌
}
window.name = '王牌对王牌';
const LIST = {
name: "跑男"
}
//直接调用
getName();
//call方法调用
getName.call(LIST);//跑男
ES6语法
let getName2 = () => {
console.log(this.name);//王牌对王牌
}
window.name = '王牌对王牌';
const LIST = {
name: "跑男"
}
//直接调用
getName2();
//call方法调用
getName2.call(LIST);//王牌对王牌 箭头函数中的this指向不能被修改
let obj = {
t1:()=>{
console.log(this);
}
}
obj.t1();
5.3 不能作为构造函数来实例化对象
构造函数是通过new关键字来生成对象实例,生成对象实例的过程也是通过构造函数给实例绑定this的过程,而箭头函数没有自己的this,创建对象过程,new
首先会创建一个空对象,并将这个空对象的__proto__
指向构造函数的prototype
,从而继承原型上的方法,但是箭头函数没有prototype
,因此不能使用箭头作为构造函数,也就不能通过new操作符来调用箭头函数。
let person = (name,age)=>{
this.name = name;
this.age = age;
}
console.log(person);
let p1 = new person();
console.log(p1); //person is not a constructor
5.4 没有自己的arguments变量
let fn = () => {
console.log(arguments); //Uncaught ReferenceError: arguments is not defined
}
fn(10, 20, 30);
5.5 没有预解析
console.log(fn); //undefined
fn(); //Uncaught TypeError: fn is not a function
var fn = () => {
}
fn();
console.log(fn); //Uncaught ReferenceError: Cannot access 'fn' before initialization
fn(); //Uncaught TypeError: fn is not a function
let fn = () => {
}
fn();
5.6 简写语法
- 当有且只有一个形参时,可以省略小括号
let add = n => {
return n*n;
}
console.log(add(10)); //100
- 当函数体内只有一条语句时候,可以省略花括号
let add = n => console.log(n);
add(10); //10
- 如果有return,必须被省略,函数体执行的结果就是函数的返回值
let add = n => n*n;
console.log(add(10)); //100
六、参数初始值
- 函数中允许形式参数中出现默认值,一般位置都在末尾
function fn(a,b,c=10){
return a+b+c;
}
let result = fn(100,200);
console.log(result);
let fn1 = ([a, b, c = 5]) => {
console.log(a, b, c);
}
fn1([1, 2, 3]);
let fn2 = ({ a, b }) => {
console.log(a, b);
}
fn2({ a: 1, b: 2 })
- 与解构赋值结合使用,解构赋值的形式先后顺序不影响
function connect({hostname,username,password,port=8080}){
console.log(hostname);
console.log(username);
console.log(password);
console.log(port);
}
connect({
hostname:'localhost',
username:"root",
password:"root",
// port:3306
})
七、rest参数
ES6中新增了rest参数,用于获取函数的实参,用来代替ES5的arguments;
参数名可以自定义,但一般正规args;
rest参数**必须放在形参列表的最后, 输出的都是纯数组 **;
作用:是将实际参数转化成一个数组
主要应用场景是一般不确定实际参数个数的函数上
语法:
function 函数名(…args){
…
}
ES5语法
function fun(){
console.log(arguments);
}
fun('白浅','折颜'); //Arguments(2) ['白浅', '折颜', callee: ƒ, Symbol(Symbol.iterator): ƒ]
ES6语法
function fun(...args){
console.log(args);
}
fun('白浅','折颜');
/*function fun(...args,a,b) { //Rest parameter must be last formal parameter
console.log(args);
}
fun('白浅', '折颜');
*/
function fun(a,b,...args) {
console.log(a);//10
console.log(b);//20
console.log(args);// ['白浅', '折颜']
}
fun(10,20,'白浅', '折颜');
八、spread扩展运算符
ES6语法中新增一种扩展运算符,可以将数组转换成可以逗号分隔的参数序列;
扩展运算符其实也算是rest参数的一种逆运算
区别:
扩展运算符 rest参数 是可以把数组展开形成逗号间隔的实际参数序列 是将实际参数转化成一个数组
数组展开
扩展运算符相当于是将arr中的元素值复制到arr1里面
如果arr中的元素是基本数据类型,则直接复制 (值复制) 你是你 我是我 后期谁更改与另一方无关不受影响
如果newArr 中的元素是引用数据类型,则复制的是对方的地址值,无论是arr还是newArr 修改了结构,将双方都受影响
let arr = [3,4,5,6,[12,13],{name:"侯志国"}];
let newArr = [...arr];
console.log(newArr);
newArr[2] = 14;
newArr[5].age = 18;
console.log(newArr);//[3, 4, 14, 6, [12, 13], { name: "侯志国",age:18 }]
console.log(arr );//[3, 4, 5, 6, [12, 13], { name: "侯志国",age:18 }]
语法:
function 函数名(){
…
}
函数名(…数组名)
//1、声明一个数组
let arr = ['金角大王','霸波奔','银叶先生','独孤求败'];
//2、声明一个箭头函数
let fn1 = (...args)=>{
console.log(args); //['金角大王', '霸波奔', '银叶先生', '独孤求败']
//注意:箭头函数内没有arguments
}
fn1(...arr);
对象展开
语法:
let 变量名 = {…对象1,…对象2,…对象n}
其实,间接也是实现了多个对象属性的合并
返回值:新对象
注意:当多个对象都相同的键名时,后者会覆盖前者
//② 对象的展开
let obj1 = {
name:"苏有朋"
}
let obj2 = {
name:"赵薇",
sex:"男"
}
let result = {...obj1,...obj2};
console.log(result); //{name: '苏有朋'}
let obj3 = {
a:1,
b:2,
c:3
}
let obj4 = {
d:4,
e:5,
a:100
}
let obj5 = {...obj3,...obj4};
console.log(obj5);//{a:100,b:2,c:3,d:4,e:5}
案例应用1:数组的合并
let arr = ['太上老君','张三丰'];
let arr1 = ['猴砸','曾毅'];
let result = [...arr1,...arr];
console.log(result);//['猴砸', '曾毅', '太上老君', '张三丰']
案例应用1:新数组克隆
let team = ['S','H','E'];
let newTeam = [...team];
console.log(newTeam); //['S', 'H', 'E']
console.log(team == newTeam); //false
案例应用2:将伪数组转为真正的数组
<body>
<button>按钮1</button>
<button>按钮2</button>
<button>按钮3</button>
<script>
//将伪数组转化为真正的数组
let btn = document.getElementsByTagName("button");
let btns = [...btn];
console.log(btns); //[button, button, button]
</script>
</body>
九、Symbol
ES6中引入了一种新的原始数据类型Symbol,表示一种独一无二的值
语法:let 变量名 = Symbol([内容]);
注意:Symbol无法进行运算,拼接等功能
<body>
<script>
//创建Symbol
let s = Symbol();
console.log(s); //Symbol()
console.log(typeof s); //symbol
//但是在实际开发中会给Symbol传递内容,这个内容可以自定义
//在Symbol中传递内容
let s1 = Symbol("内容");
console.log(s1); //Symbol(内容)
</script>
</body>
Symbol 的值是唯一的
let a = 100;
let b = 100;
console.log(a===b); //true
let s1 = Symbol();
let s2 = Symbol();
console.log(s1===s2); //false
//即使给Symbol中添加相同的内容也是两个不同的Symbol
let s3 = Symbol('html');
let s4 = Symbol('html');
console.log(s3===s4); //false
Symbol值不能与其他数据类型进行计算
let s2 = Symbol("内容1");
console.log(s2+100); //Uncaught TypeError: Cannot convert a Symbol value to a number
console.log(s2+'200');//Uncaught TypeError: Cannot convert a Symbol value to a string
Symbol添加属性
//向对象中添加方法 up down
let game = {
name:'俄罗斯方块',
};
//给对象添加属性的普通做法
// 上
game.up = function(){
console.log('上1')
}
game.up = function(){
console.log('上2')
}
console.log(game)
game.up()
//使用Symbol
let game = {
name:'俄罗斯方块',
};
//方法1
//给对象创建独一无二的属性
//1.定义一个Symbol
let up = Symbol('up');
let shang = Symbol('up');
//2.给对象添加Symbol的属性
game[up] = function(){
console.log('上1')
}
game[shang] = function(){
console.log('上2')
}
//3.调用
game[up]();
game[shang]();
console.log(game)
//方法2
let down = Symbol('xia');
let xia = Symbol('xia');
let game = {
name: '俄罗斯方块',
//注意:属性名一定要[],不然就变成了普通的做法了
[down]: function () {
console.log('下1')
},
[xia]: function () {
console.log('下2')
}
};
game[down]()
game[xia]()
console.log(game)
十、iterator迭代器
迭代器(iterator)是一种接口,为各种不同的数据结构提供统一的访问机制。
任何数据结构只要部署iterator接口,就可以完成遍历操作
。
- ES6创建了一种新的遍历命令for…of循环,iterator几口主要供for…of循环(相互依赖)
- 原生具备iterator接口的数据(可以用for of遍历)
- Array
- Arguments
- Set
- Map
- String
- NodeList
- HTMLCollection
- TypedArray(用来操作二进制的数据)
//1.数组
var arr = [1,2,3];
console.log(arr);
console.log(arr[Symbol.iterator]);
//遍历
for(let item of arr){
console.log(item); //item表示数组中的元素值
}
//2.函数arguments
function fun(){
// console.log(arguments); //Arguments(3) [1, 2, 3... Symbol(Symbol.iterator): ƒ]
for(var item of arguments){
console.log(item); // 1 2 3
}
}
fun(1,2,3);
//3.字符串
var str = new String('hello');
console.log(str);
for(var item of str){
console.log(item); //h e l l o
}
//4.NodeList
var btns1 = document.querySelectorAll('button');
console.log(btns1);
for(var item of btns1){
console.log(item);
}
//5.HTMLCollection
let btns = Array.from(document.getElementsByTagName('button'));
console.log(btns);
//entries() 方法返回一个数组的迭代对象,该对象包含数组的键值对 (key/value)。
//迭代对象中数组的索引值作为 key, 数组元素作为 value。
for(let [item,index] of btns.entries()){
console.log(item);
console.log(index);
}
var arr1 = [1,2,3];
for(var [index,item] of arr1.entries()){
console.log(index);
}
var btns2 = document.getElementsByName('btn');
console.log(btns2);
for(var [index,item] of btns2.entries()){
console.log(item);
console.log(index);
}
var btns3 = document.querySelectorAll('button');
console.log(btns2);
for(var [index,item] of btns3.entries()){
console.log(item);
console.log(index);
}
注意:for of和for in的相同点和不同点:
相同点:都是用来遍历数据的
不同点:
1)for of是ES6中提出的新的遍历方式,只要是数据中有iterator接口,才可以进行for of
2)如果for of 遍历数组,item则是数组中的每一个元素
如果for in遍历数组,item则是数组的每个元素的下标 arr[item] 元素值
3)for of 不能遍历对象,但是for in可以遍历对象
4)for of 本身是无法获取数组下标的,但是可以借助于entries()方法
- 工作原理
- 创建一个指针对象,指向当前数据结构的起始位置
- 第一次调用对象的next方法,指针自动指向数据结构的第一个成员
- 接下来不断调用next方法,指针一直向后移动,直到指向最后一个成员
- 每调用next方法返回一个包含value和done属性的对象
- value表示循环的值
- done的值默认为false,当迭代完毕时就会显示为true
var arr = ['晓沫','小沫','大沫沫'];
console.log(arr[Symbol.iterator]()); //Array Iterator {}
var str = "hello";
console.log(str[Symbol.iterator]()); //StringIterator {}
//创建一个指针对象
let iterator = arr[Symbol.iterator]();
//调用这个对象下的原型上的next方法
console.log(iterator.next()); //{value: '晓沫', done: false}
console.log(iterator.next()); //{value: '小沫', done: false}
console.log(iterator.next()); //{value: '大沫沫', done: false}
console.log(iterator.next()); //{value: undefined, done: true}
- 自定义迭代器
let obj = {
name:'洪家班',
teams:[
'洪金宝',
'林正英',
'钟发',
'元彪'
]
}
//对象中的数组,只要是数组,无论在哪定义只要有迭代器,都可以使用for of迭代遍历
/* for(let item of obj.teams){
console.log(item);
} */
//但是如果想要直接循环obj对象就不行了
//原因是:for of无法直接迭代对象
//但是咱们可以自定义一个迭代器,让其可以循环
let obj = {
name:'洪家班',
teams:[
'洪金宝',
'林正英',
'钟发',
'元彪'
],
[Symbol.iterator]:function(){
//返回值为指针对象(实质就是一个对象)
//定义一个下标
let index = 0;
return {
next:function(){
//需要确定value的值
let data = obj.teams[index];
//定义一个可以模拟是否迭代完毕的变量,默认值为false
let done = false;
//当每一次循环迭代完毕之后,就需要让done的值变成true
if(index>=obj.teams.length){
done = true;
}
//让下标递增
index++;
//返回值:另一个对象,里面包含value和done属性
return {
value:data,
done
}
}
}
}
}
//遍历obj对象
for(let item of obj){
//打印对象里面的值
console.log(item);
}
注意:在上段代码中函数里面使用的是obj变量,请问能否修改成this,以及修改之后产生的问题?
解决this指向的办法1:将匿名函数修改成箭头函数
next:()=>{
//需要确定value的值
let data = obj.teams[index];
//定义一个可以模拟是否迭代完毕的变量,默认值为false
let done = false;
//当每一次循环迭代完毕之后,就需要让done的值变成true
if(index>=obj.teams.length){
done = true;
}
//让下标递增
index++;
//返回值:另一个对象,里面包含value和done属性
return {
value:data,
done
}
}
解决this指向的办法2:保存this
[Symbol.iterator]:function(){
//返回值为指针对象(实质就是一个对象)
//定义一个下标
let index = 0;
let _this = this;
return {
next:function(){
//需要确定value的值
let data = _this.teams[index];
//定义一个可以模拟是否迭代完毕的变量,默认值为false
let done = false;
//当每一次循环迭代完毕之后,就需要让done的值变成true
if(index>=_this.teams.length){
done = true;
}
//让下标递增
index++;
//返回值:另一个对象,里面包含value和done属性
return {
value:data,
done
}
}
}
}
解决this指向的办法3:修改this指向
return {
next:function(){
//需要确定value的值
let data = this.teams[index];
//定义一个可以模拟是否迭代完毕的变量,默认值为false
let done = false;
//当每一次循环迭代完毕之后,就需要让done的值变成true
if(index>=this.teams.length){
done = true;
}
//让下标递增
index++;
//返回值:另一个对象,里面包含value和done属性
return {
value:data,
done
}
}.bind(obj)
}
十一、集合之set
一直以来,JS只能使用数组
和对象
来保存多个数据,缺乏像其他语言那样拥有丰富的集合类型。
因此,ES6新增了两种集合类型(set 和 map),用于在不同的场景中发挥作用。
Set是一个不重复值
的集合,而且Set只有值没有键
1.1 创建set集合
//创建一个set集合,可以是空集合,也可以是非空集合
let s1 = new Set();
console.log(s1); //Set(0) {size: 0}
//如果是非空集合,那么set中需要传入的参数是可以有迭代器的类型值,不能直接将多个数字作为参数
//例如:字符串、数组
let s2 = new Set([1,2,3]);
console.log(s2); //Set(3) {1, 2, 3}
//数组还可以实现去重的功能
let s3 = new Set([1,4,5,6,5,6,7,6,6,7,5,3,2,5,2]);
console.log(s3); //Set(7) {1, 4, 5, 6, 7, …}
11.2 操作set集合
11.2.1 两个属性
Set.prototype.constructor:构造函数,默认就是Set函数。
Set.prototype.size:返回Set实例的成员总数。
11.2.2 四个操作方法
Set.prototype.add(value):添加某个值,返回 Set 结构本身。
Set.prototype.delete(value):删除某个值,返回一个布尔值,表示删除是否成功。
Set.prototype.has(value):返回一个布尔值,表示该值是否为Set的成员。
Set.prototype.clear():清除所有成员,没有返回值。
let s1 = new Set([1,2,3,4,5,6]);
//元素的个数
// console.log(s1.size);
//添加
// s1.add(7);
// console.log(s1);
//删除
// s1.delete(7);
// console.log(s1);
//检测集合中是否包含某个元素
// console.log(s1.has(4));
//清空
// s1.clear();
// console.log(s1);
//for of遍历
/* for(var item of s1){
console.log(item);
} */
//扩展运算符
let arr = [...s1];
console.log(arr);
//对比
console.log(s1);
11.3 set集合的应用场景
-
去除数组重复成员。
//创建一个数组 let arr = [4,5,6,7,2,3,4,1,2,3,1]; //创建一个set集合去重,并在数组中展开 let newArr = [...new Set(arr)]; //输出结果 console.log(newArr); //[4, 5, 6, 7, 2, 3, 1]
-
交集(找出两个set集合中相同的数据部分)
普通版
//1.创建两个数组 let arr1 = [1,1,3,3,4,2,2]; let arr2 = [3,3,4,4,5,6,7]; //2.创建两个set集合实现去重(过程1) // console.log(new Set(arr1)); // console.log(new Set(arr2)); //3.让其中一个集合中去重后的结果展开到真数组中(过程2) // console.log([...new Set(arr1)]); //4.在这个数组中过滤值(过程3) //[1,3,4,2] let newArr = [...new Set(arr1)].filter(function(item){ //item为arr1去重后的每一个元素 let s = new Set(arr2); // console.log(s); //Set(5) {3, 4, 5, 6, 7} //打印s集合中是否包含了item中的值 // console.log(s.has(item)); //false true true false //判断如果包含了item的值则返回true,否则返回false if(s.has(item)){ return true; }else{ return false; } }) console.log(newArr); //[3, 4]
升级版
let s = new Set(arr2); let newArr1 = [...new Set(arr1)].filter(item=>s.has(item)); console.log(newArr1);
-
并集(将两个集合去重后的结果放在一起)
//1.创建两个数组 let arr1 = [1,1,3,3,4,2,2]; let arr2 = [3,3,4,4,5,6,7]; //2.每一个数组去重 console.log(new Set(arr1)); //{1, 3, 4, 2} console.log(new Set(arr2)); //Set(5) {3, 4, 5, 6, 7} //3.将两个集合都存入一个真实的数组中,使用扩展运算符 console.log([...new Set(arr1),...new Set(arr2)]); //[1, 3, 4, 2, 3, 4, 5, 6, 7] //4.在将这个数组去重 console.log(new Set([...new Set(arr1),...new Set(arr2)])); //Set(7) {1, 3, 4, 2, 5, …} //5.在将这个集合存入一个真实的数组中 let newArr = [...new Set([...new Set(arr1),...new Set(arr2)])]; // [1, 3, 4, 2, 5, 6, 7] console.log(newArr); //[1, 3, 4, 2, 5, 6, 7]
-
差集(两个集合中相对找不一样的部分)
基础版
//1.创建两个数组 let arr1 = [1,1,3,3,4,2,2]; let arr2 = [3,3,4,4,5,6,7]; //2.每一个数组去重 console.log(new Set(arr1)); //{1, 3, 4, 2} console.log(new Set(arr2)); //Set(5) {3, 4, 5, 6, 7} //3.在第一个集合中过滤出来相对集合2里面不一样的部分 let newArr = [...new Set(arr1)].filter(function(item){ //item是arr1集合过滤之后的所有的值 // console.log(item);//1 3 4 2 //在第2个集合中查找相对第1个集合里面不一样的部分 let s2 = new Set(arr2); if(s2.has(item)){ return false; }else{ return true; } }) console.log(newArr); //) [1, 2] let s1 = new Set(arr1); let newArr1 = [...new Set(arr2)].filter(function(item){ return !s1.has(item); }) console.log(newArr1); // [5, 6, 7]
升级版1
let s2 = new Set(arr2); let newArr2 = [...new Set(arr1)].filter(item=>{ return !s2.has(item); }); console.log(newArr2);
升级版2
let s3 = new Set(arr2); let newArr3 = [...new Set(arr1)].filter(item=>!s3.has(item)); console.log(newArr3);
十二、集合之map
Map与Set的数据类型相似,他们都是一个集合,但是map是一个键值对集合。
Map 数据结构类似于对象,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。
Map方法里面需要传入一个二维数组
来形成键值对
12.1 创建map集合
let m0 = new Map();
console.log(m0); //Map(0) {size: 0}
//map参数为二维数组
let m1 = new Map([['name','张三'],['age',18],['address','北京市']]);
console.log(m1); //Map(3) {'name' => '张三', 'age' => 18, 'address' => '北京市'}
12.2 操作map集合
let m0 = new Map();
console.log(m0); //Map(0) {size: 0}
//map参数为二维数组
let m1 = new Map([['name','张三'],['age',18],['address','北京市']]);
console.log(m1); //Map(3) {'name' => '张三', 'age' => 18, 'address' => '北京市'}
//操作map集合
//添加
m1.set('height',180);
console.log(m1); //Map(4) {'name' => '张三', 'age' => 18, 'address' => '北京市', 'height' => 180}
//获取
console.log(m1.get('name')); //张三
console.log(m1.get('age')); //180
console.log(m1.get('address')); //北京市
let obj1 = {'hobby':['抽烟','喝酒','烫头']};
m1.set(obj1,'于谦')
console.log(m1); //Map(5) {'name' => '张三', 'age' => 18, 'address' => '北京市', 'height' => 180, {…} => '于谦'}
console.log(m1.get(obj1)); //于谦
//删除
m1.delete('address');
console.log(m1); //Map(4) {'name' => '张三', 'age' => 18, 'height' => 180, {…} => '于谦'}
//检测
console.log(m1.has('address')); //false
console.log(m1.has(obj1)); //true
//元素个数
console.log(m1.size); //4
//清空
// m1.clear();
// console.log(m1);
//for of循环
for(let item of m1){
//是二维数组中的每一个小数组
console.log(item);
}
十三、类与对象
ES6提供了更接近传统语言的写法,引入了类(class)的概念,作为对象的模板;
通过class关键字,可以定义类,class就可以被看作是一个语法糖;
ES6只是在ES5基础之上让对象原型写法更加清晰,更像面向对象的语法而已;
class中的知识点:
-
class(声明类)
-
constructor(定义构造函数初始化)
-
static(定义静态方法和属性)
-
super(调用父类构造方法)
-
extends(继承父类)
-
父类方法可以被重写
13.1 定义类
语法:
class 类名{
构造方法(构造器)
constructor(){
}
方法名(){
}
}
注意:
1、构造器名称不可以修改
2、定义的普通方法语法必须使用ES6的语法,不能使用ES5的完整形式
13.2 实例对象
let 对象名 = new 类名([参数1,参数2…])
回顾ES5语法
<body>
<script>
//ES5语法:
function Student(name,age){
//在对象上添加属性
this.name = name;
this.age = age;
}
//原型对象上添加方法
Student.prototype.study = function(){
console.log('每一个学生都可以学习');
}
Student.prototype.play = function(){
console.log('每一个学生都可以玩');
}
//实例化对象
let s1 = new Student('小明',10);
console.log(s1); //Student {name: '小明', age: 10}
</script>
</body>
ES6语法
//首先搞清楚一个概念:函数在类外还叫函数,在类内就叫方法
//1、声明一个类
class Student{
//4、在实例化对象时,想要对于每一个对象的细节进行区分,需要在类声明的时候,建立对象属性
//class类中有一个特殊的方法(构造方法),每一个类有且只有一个,方法中的this指向新创建的对象
//一般情况下,构造函数都会放在类声明的开头,然后是一些其他的方法
constructor(name,age){
this.name = name;
this.age = age;
}
//2、声明方法(可以使用ES6方法简化写法)
study(){
console.log('每一个学生都可以学习');
}
play(){
console.log('每一个学生都可以玩');
}
}
//3、实例化对象
//记住:ES5和ES6中只有类声明的方式不同,实例化对象写法是不变的
let s1 = new Student('王二麻子',20);
console.log(s1);//Student {name: '王二麻子', age: 20}
//5、获取对象的属性值
console.log(s1.name); //王二麻子
console.log(s1.age); //20
//6、调用对象下的方法
s1.study();//每一个学生都可以学习
s1.play();//每一个学生都可以玩
13.3 static静态成员
之前添加的属性都是给新创建的对象,如果想要在类身上添加属性,那么这个属性叫静态属性(静态成员)
回顾ES5语法
//ES5语法
//给函数对象添加成员(属性、方法)
//1、声明一个函数对象
function Student(){}
//2、给函数对象添加属性
Student.sname = "测试用户";
//3、给函数对象添加方法
Student.study = function(){
console.log('每一个学生都特别爱学习');
}
//4、实例化对象
let s1 = new Student();
console.log(s1);//Student {}
console.log(s1.sname); //undefined 由于name和study放在函数身上,并不属于实例化对象,所以无法操作
//如果想要获取到值,需要从函数身上获取,不能从实例化对象身上
console.log(Student.sname); //测试用户
console.log(Student.study); //f(){}
class 类名{
static 属性名 = 属性值;
static 方法名(){}
}
//ES6语法
class Student{
//声明静态成员(属性、方法)
//属性
static sname = "测试用户";
//方法
static study(){
console.log('每一个学生都特别爱学习');
}
}
//实例化对象
let s1 = new Student();
console.log(s1);
console.log(s1.sname); //undefined
//如果想要获取,还是需要找类的成员,不是对象的成员
console.log(Student.sname);//测试用户
console.log(Student.study);//f(){}
Student.study(); //每一个学生都特别爱学习
13.4 对象继承
回顾ES5语法
<body>
<script>
//ES5继承
//人类
function Person(name,age){
this.name = name;
this.age = age;
}
//父类原型下的方法
Person.prototype.eat = function(){
console.log('民以食为天');
}
//学生类
function Student(num,n,a,s){
Person.call(this,n,a,s);
this.sno = num;
}
Student.prototype = new Person();
//修正构造器
Student.prototype.constructor = Student;
//子类原型下的方法
Student.prototype.study = function(){
console.log('学习可以改变人生');
}
let s1 = new Student('小明',10,'001');
console.log(s1);
//获取对象下的属性
console.log(s1.name); // 小明
console.log(s1.age); //10
console.log(s1.sno); // 001
//调用对象的方法
s1.study(); //本类
s1.eat(); //父类
</script>
</body>
ES6语法
<body>
<script>
//1、先定义父类
class Person{
//构造方法(并不是简化写法,这是一种固定语法)
constructor(name,age){
this.name = name;
this.age = age;
}
//其他方法(可以使用方法的简化写法)
eat(){
console.log('民以食为天');
}
}
//2、在定义子类并继承于Person父类
//继承:class 子类 extends 父类{}
//当子类继承于父类的时候,构造方法也会同时继承过来
class Student extends Person{
//构造方法:注意形参顺序:当子类构造函数自身扩展的参数需要后置,否则顺序就会错乱
constructor(name,age,num){
//因为在父类中已经定义过属性,在子类无需再重复定义
//只需要调用父类的构造方法
super(name,age);
//还可以扩展子类自己的属性,this指向的是子类的实例对象
this.sno = num;
}
//子类的方法
study(){
console.log('学习可以改变人生');
}
}
let s1 = new Student('小明',10,'001');
console.log(s1); //Student {name: '小明', age: 10, sno: '001'}
//调用本类方法
s1.study(); //学习可以改变人生
//调用父类方法
s1.eat(); //民以食为天
</script>
</body>
13.5 class类的get和set方法
class Phone {
constructor(brand) {
this.brand = brand;
}
//getter setter 是动态操作对象的属性
//获取价格
get price() {
return this.abc;
}
//设置动态的价格
set price(v) {
this.abc = v;
}
}
let huawei = new Phone('华为');
huawei.price = 5000;
console.log(huawei)
console.log(huawei.price)
13.2 对象的深浅拷贝
拷贝就是复制,或者还可以叫克隆,发生在对象
和数组
类型上。
拷贝的核心:通过某种操作对老对象进行复制的操作,并生成了新对象,那么这就是拷贝。
区分浅拷贝和深拷贝:
- 如果修改了新对象的属性,老对象的属性也修改了,这个时候叫浅拷贝
- 如果修改了新对象的属性,老对象的属性不修改,这个时候叫深拷贝
13.2.1 浅拷贝
- 直接赋值的方式
//数组
let arr = [1,2,3];
let arr1 = arr;
arr1[0] = 10;
console.log(arr); // [10, 2, 3]
console.log(arr1); // [10, 2, 3]
//对象
let obj = {
name:"张三",
age:23
}
let obj1 = obj;
obj1.age = 33;
console.log(obj); //{name: '张三', age: 33}
console.log(obj1); //{name: '张三', age: 33}
-
通过数组中的方法
- concat
//数组中的方法 /* let arr = [1,2,3,4]; let newArr = arr.concat([10,20,30]); console.log(arr == newArr); //false arr[0] = 100; console.log(newArr); //[1, 2, 3, 4, 10, 20, 30] console.log(arr); //[100, 2, 3, 4] */ //但是要注意:如果原数组中的元素是引用数据类型则修改其中一个,另一个也会受影响 let arr = [1,{name:'admin'},3,4]; let newArr = arr.concat(); console.log(arr == newArr); //false arr[1].name = '张三' console.log(newArr[1]); //{name: '张三'} console.log(arr[1]); //{name: '张三'}
- slice
let arr = [1,2,3,4];
let newArr = arr.slice(0);
console.log(arr); //[1, 2, 3, 4]
console.log(newArr); //[1, 2, 3, 4]
arr[0] = 10;
console.log(arr); //[10, 2, 3, 4]
console.log(newArr); //[1, 2, 3, 4]
//还是要注意数组中元素为引用数据类型
let arr1 = [1,[20],3,{a:100}];
let arr2 = arr1;
arr2[1] = 30;
arr1[3].a = 200;
console.log(arr1); //[1, 30, 3, {…}]
console.log(arr2); //[1, 30, 3, {…}]
- 扩展运算符
//扩展运算符
let arr = [3,{name:'张三'},5,6];
let newArr = [...arr];
console.log(arr); //[3, {…}, 5, 6]
console.log(newArr); //[3, {…}, 5, 6]
//修改新数组的内容
newArr[1].name = '李四';
console.log(arr[1]); //{name: '李四'}
console.log(newArr[1]); //{name: '李四'}
- 通过对象Object中的assign方法进行合并
let obj = {
name:"老于",
age:50,
hobby:['抽烟','喝酒','烫头']
}
let newObj = Object.assign({},obj);
//修改对象的属性
newObj.age = 55;
newObj.hobby.push('说相声');
newObj.hobby[0] = 'smoke';
console.log(obj);
console.log(newObj);
13.2.2 深拷贝
利用JSON的方式
//使用JSON实现深拷贝
let obj = {
name:'尚硅谷',
address:['北京','武汉','上海','西安','深圳']
}
//把obj对象转化成JSON字符串
let str = JSON.stringify(obj);
//把JSON字符串 转化成一个新的对象
let newObj = JSON.parse(str);
//修改新对象的属性
newObj.address[0] = 'beijing';
console.log(obj); //{name: '尚硅谷', address: ['北京','武汉','上海','西安','深圳']}
console.log(newObj); //{name: '尚硅谷', address: ['beijing','武汉','上海','西安','深圳']}
注意:虽然JSON方式实现深拷贝很容易,但是容易有问题【面试中爱问的地方】
let obj = {
name:"老于",
age:50,
hobby:['抽烟','喝酒','烫头'],
//不能针对方法的拷贝
test:function(){
console.log('测试');
}
}
let str = JSON.stringify(obj);
console.log(str); //{"name":"老于","age":50,"hobby":["抽烟","喝酒","烫头"]}
let newObj = JSON.parse(str);
console.log(newObj); //{name: '老于', age: 50, hobby: Array(3)}
13.2.2.1 封装函数前的分析
//老对象
let obj = {
name: 'atguigu',
pos: ['北京', '上海', '深圳', '武汉', '西安'],
founder: {
name: '刚哥',
age: 35
},
test: function () {
console.log('测试')
}
}
//分析
//1.准备一个容器(对象 数组)
let newObj = {}
//2.给新对象添加属性 name
newObj.name = obj.name;
//3.给新对象添加属性 pos
// newObj.pos = obj.pos;
newObj.pos = []
newObj.pos[0] = obj.pos[0]
newObj.pos[1] = obj.pos[1]
newObj.pos[2] = obj.pos[2]
newObj.pos[3] = obj.pos[3]
newObj.pos[4] = obj.pos[4]
//4.给新对象添加属性 founder
// newObj.founder = obj.founder;
newObj.founder = {}
newObj.founder.name = obj.founder.name;
newObj.founder.age = obj.founder.age;
//4.给新对象添加方法 test
newObj.test = obj.test.bind(newObj);
// newObj.test = obj.test;
console.log(newObj.test)
console.log(obj.test)
console.log(newObj.test === obj.test)
obj.test();
newObj.test();
function fn(){
console.log(this);
}
let f = fn.bind();
console.log(f);
console.log(fn);
console.log(f == fn);
13.2.2.2 深拷贝函数的封装
先准备一个对象
//老对象
let obj = {
name : 'atguigu',
pos : ['北京','上海','深圳','武汉','西安'],
founder : {
name : '刚哥',
age : 35
},
test:function(){
console.log('测试')
}
}
在封装一个可以判断所有数据类型的函数
//封装函数:判断数据类型的
function getDataType(data) {
if (typeof data === 'object') {
if (data instanceof Array) {
//数组
return 'Array';
} else {
//对象
return 'Object';
}
} else if (typeof data === 'function') {
//函数
return 'Function';
} else {
//num str bool (基本数据类型)
return false;
}
}
//测试数据
let str = '';
console.log(getDataType(str)); //false
let arr = [];
console.log(getDataType(arr)); //Array
let obj = {};
console.log(getDataType(obj)); //Object
let f = function(){}
console.log(getDataType(f)); //Function
最后封装函数
//封装深拷贝函数
function deepClone(oldObj) {
//1.准备一个容器(对象 数组)
let wrapper;
//2.根据老对象判断数据类型,决定容器是 {} 还是 []
let type = getDataType(oldObj)
if (type === 'Array') {
wrapper = [];
}
if (type === 'Object') {
wrapper = {}
}
//3.给容器添加新属性
//枚举老对象的属性
for (let item in oldObj) {
//item 是对象的每一个属性
// wrapper[item] = '123'
//根据属性值的数据类型去添加
let typeItem = getDataType(oldObj[item])
if (typeItem === 'Object' || typeItem === 'Array') {
//如果是对象或者数组,
wrapper[item] = deepClone(oldObj[item]);
} else if (typeItem === 'Function') {
//如果是函数,通过 bind()去添加
wrapper[item] = oldObj[item].bind(wrapper)
} else {
//如果是普通的数据类型,直接添加
wrapper[item] = oldObj[item];
}
}
//返回值:是新对象
return wrapper;
}
//新对象
let newObj = deepClone(obj)
console.log(newObj)
十四、数值扩展
//1.二进制、八进制、十进制、十六进制
//1) 二进制:以0b开头 ,表示的数字范围在0-1之间
let num = 0b1101;
console.log(num); //实际输出的值的进制都是十进制(二进制转化成十进制) // 13
/*
* 1 1 0 1
* 2^3 2^2 2^1 2^0
* 8 4 2 1
*
* 1*8 + 1*4 + 0*2 + 1 * 1 = 13
*/
let num1 = 0b011001;
console.log(num1); // 25
/*
* 0 1 1 0 0 1
* 2^5 2^4 2^3 2^2 2^1 2^0
* 32 16 8 4 2 1
*
* 0*32 + 1*16 + 1*8 + 0*4 + 0*2 + 1 * 1
* 0 + 16 + 8 + 0 + 0 + 1 = 25
*/
//2) 八进制:以0o开头,表示的数字范围在0-7之间
let num3 = 0o123;
console.log(num3); //八进制转换成十进制 83
/*
* 1 2 3
* 8 8 8
* 8^2 8^1 8^0
* 64 8 1
*
* 1*64 + 2*8 + 3*1 = 64 + 16 + 3 = 83
*/
//3) 十进制: 表示的数字范围在0-9之间
let num4 = 100;
console.log(num4);
//4) 十六进制:表示的数字范围在0-15之间 0-9 a-f
let num5 = 0xff;
console.log(num5); //255
/*
* 15 15
* 16 16
* 16^1 16^0
* 15 * 16 + 15 = 255
*/
//Number.isFinite 检查一个数字是否是有限数
/**
* 提示:
* 如果 number 是 NaN,或者是正、负无穷大的数,则返回 false。
* Number.isFinite()与isFinite() 函数不同,isFinite() 会先把检测值转换为 Number ,然后在检测。
* Number.isFinite() 不会将检测值转换为 Number对象,
* 如果检测值不是 Number 类型,则返回 false,否则为true
*/
Number.isFinite(123) //true
Number.isFinite(-1.23) //true
Number.isFinite(5-2) //true
Number.isFinite(0) //true
Number.isFinite('123') //false
Number.isFinite('Hello') //false
Number.isFinite('2005/12/12') //false
Number.isFinite(Infinity) //false
Number.isFinite(-Infinity) //false
Number.isFinite(0 / 0) //false
//Number.isNaN 检查一个数值是否为NaN
//注意:isNaN(window.isNaN)与Number.isNaN的区别
/*
* isNaN:将参数转换成数字,这个方法是ES5的
* 作用:通过Number方法把参数转换成数字类型,如若转换成功,则返回false,反之返回true
* Number.isNaN: 不会自行将参数转换成数字, 这个方法是ES6的
* 作用:用来判断一个值是否严格等于NaN,
* 它会首先判断传入的值是否为数字类型,如不是,直接返回false。
*
* 区别:
* isNaN方法首先转换类型,而Number.isNaN方法不用;
* isNaN不能用来判断是否严格等于NaN,Number.isNaN方法可用
*/
console.log(Number.isNaN(NaN)); //true
console.log(Number.isNaN(20)); //false
console.log(Number.isNaN(20 + undefined)); //true
//Number.parseInt 字符串转整数
console.log(Number.parseInt('123456')); //123456
console.log(Number.parseInt('123456fdsfsfsf')); //123456
console.log(Number.parseInt('fdsfsfsf123456')); //NaN
//Math.trunc 将数字的小数部分抹掉
console.log(Math.trunc(45.56)); //45
console.log(Math.trunc(10/3)); //3
console.log(Math.trunc(Math.PI)); // 3
//Number.isInteger 判断一个数是否为整数 is[是否] Integer[整型]
console.log(Number.isInteger(10)); //true
console.log(Number.isInteger(10.6)); //false
//Math.pow() 幂运算
let num = Math.pow(2,3);
console.log(num); //8
let num1 = Math.pow(3,2);
console.log(num1); // 9
//ES7中的写法
let n1 = 2;
let n2 = 3
let result = n1 ** n2;
console.log(result); // 8
十五、对象扩展
15.1 Object.is() 判断两个值是否完全相等
console.log(Object.is(100,100)); //true
console.log(Object.is(100,'100')); //false
console.log(Object.is(NaN,NaN)); //true 【特殊】 只要两个值是一样就认为是相等的
console.log(Object.is(undefined,undefined));//true
console.log(Object.is(undefined,NaN)); //false
console.log(Object.is(undefined,null)); //false
console.log(NaN === NaN); //false
console.log(undefined == NaN); //false
console.log(undefined === NaN); //false
console.log(undefined === null); //false
console.log(undefined == null); //true undefined派生自null
15.2 Object.assign() 对象的合并
//Object.assign 对象的合并
let obj1 = {
a:1,
b:2
}
let obj2 = {
c:3,
d:4,
a:5
}
//合并的原理:后者对象向前者对象合并,当如果两个对象合并的时候出现了相同的键名,则是后者对象的键值
let newObj = Object.assign(obj1,obj2);
console.log(newObj);
console.log(obj1);
console.log(obj2);
console.log(obj1 == newObj); //true
console.log(obj2 == newObj); //false
//扩展运算符
let newObj1 = {...obj1,...obj2}; //{a:5,b:2,c:3,d:4}
console.log(newObj1);
console.log(newObj1 == obj1); //false
console.log(newObj1 == obj2); //false
15.3 直接修改 proto 设置原型
let obj1 = {
a:1,
b:2
}
let obj2 = {
c:3,
d:4
}
obj1.__proto__ = obj2;
console.log(obj1);