Js高级笔记
箭头函数
优点
(1)简洁的语法
(2)隐式返回,如 下面的代码可以去掉return,代码移到一行,减少代码量numbers.map((number)=>number*2)
(3)解决了this的指向问题,原生的写法this指向的是调用者,箭头函数this绑定的是定义时的那个对象。如果有对象嵌套的情况,则this绑定到最近的一层对象上
(4) 箭头函数没有 prototype 属性,不能进行 new 实例化,亦不能通过 call、apply 等绑定 this;
(5) 在定义类的方法时,箭头函数不需要在 constructor 中绑定 this。
缺点
(1)做为构造函数的时候不能使用箭头函数
(2)真正需要this的时候如给元素绑定click事件的 时候,执行的回调函数不能使用箭头函数。
(3)我们需要使用arguments对象的时候不能使箭头函数。箭头函数中没有arguments对象。
(4)对象的方法也不可以使用箭头函数
箭头函数是匿名函数,一般做为参数传递
-
没有形参、没有返回值
const func = () => { console.log('执行业务'); }; -
没有形参、没有返回值、业务只有一行代码 花括号都可以省略
如果没有参数,()也不能省略
const func = () => console.log('执行业务'); -
只有一个形参、没有返回值、业务只有一行代码(括号能省略)
1.const func = num => console.log(num + 1); 2.const func = (num) => console.log(num + 1); -
两个或者多个参数(括号不能省略)、没有返回值、业务只有一行代码
const func = (a, b) => console.log(a + b); -
没有形参,有返回值 业务两行代码
const func = () => { let a = 100; return a + 100; }; -
没有形参、有返回值,业务一行代码
const func7 = () => { return 100 + 200; }; -
没有形参、有返回值,业务一行代码 等价上述6的写法
const func7 = () => 100 + 200; // 相等于 return 100+200 -
例子
const button = document.querySelector('button'); button.addEventListener('click', () => { console.log(123321); });
面向对象
面向过程和面向对象
面向过程
概述:
面向过程就是分析出实现需求所需要的步骤,通过函数一步步实现这些步骤,然后依次调用 。
- 优点:性能比面向对象好,因为类调用时需要实例化,开销比较大,比较消耗资源。 缺点:不易维护、不易复用、不易扩展。
面向对象
概述:
- 面向对象是把整个需求按照特点、功能划分,将这些存在共性的部分封装成对象,创建了对象不是为了完成某一个步骤,而是描述某个事物在解决问题的步骤中的行为。
- 一种编程行业通用的写
项目级的代码的思维,引导我们如何编写高质量的代码,万物皆对象 -看待事物的角度,(属性:数据,行为:动作(方法))。代码特点是封装和继承
- 优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统 更加灵活、更加易于维护 。
- 缺点:性能比面向过程差 。
字面量-创建对象
const obj = {}
工厂函数
无法实现继承
function createPerson(name) {
return {name:name}
}
构造函数
- 构造函数的首字母大写 - 行内编码规范
- 构造函数想要解决 性能问题
- 一定会
把方法-函数写在构造函数的外面 - 再通过this.say 指向外部的函数
工作原理
- 开辟空间
- 将新的创建的对象指向构造函数中的this
- 为对象赋值
- 将创建好的对象的地址返回
function say() {
console.log('这个是say方法');
}
function CreatePerson(name) {
this.name = name; // 创建了name属性
// this.say = function () {
// console.log('这个是say方法');
// };
this.say = say; // say函数引用类型, 构造函数中的say 其实和外面的say内存地址一致的 同一个say方法
}
const obj1 = new CreatePerson('悟空');
const obj2 = new CreatePerson('八戒');
// obj1.say();
// obj2.say();
// 两个say的判断比较 是false 说明 两个say是在不同的内存空间上
// 两个say 占用了两个内存空间
// console.log(obj1.say === obj2.say); // false
console.log(obj1.say === obj2.say); // true
// 对于基本类型来说,= 就是复制了一份值
// let num=100;
// let num2=num;// 复制值 num和 num2占两给内存 各自不影响
// num2=1000;
// console.log(num);
// 对于引用类似 = 其实把地址拷贝了一份给新的对象 两个数据 公用一份数据
// let person1 = { name: '悟空' }; // person1 指向了 一个地址 0x1111 存放悟空
// let person2 = person1; // person2也指向了person1的地址 0x1111 person1和person2 通用一个数据
// 修改了person2 person1也会发生改变
// person2.name = '八戒';
// console.log(person1);
// person2 和person1 共同指向了同一个地址
// console.log(person1 === person2); // true
// let o={};// 开辟了一个内存
// let o2={};// 开辟了另外一个内存
// console.log(o===o2);// false 两个不同的内存地址
构造函数的全局污染
// 构造函数的 方法 都会通过类似的这种方式 来实现 多个实例的方法共享
function say() {
console.log('你好');
}
function CreatePerson(name) {
this.nickname = name;
this.say = say;
}
// function say() {
// console.log("学生你好");
// }
// function createStudent(params) {
// }
const obj1 = new CreatePerson('悟空');
const obj2 = new CreatePerson('八戒');
console.log(obj1);
console.log(obj2);
console.log(obj1.say === obj2.say); // true 这个代码是合理的 优化过
console.log(obj1.say === obj2.say); // false 不够好 性能不够好 两个say占据了两个内存
// 函数函数 方法提取出去 这套代码
// 优点: 方便代码维护、也解决了性能 obj1.say === obj2.say
// 缺点: 代码不够优雅 污染了全局变量 以后不能写 say方法,很容易就覆盖()
构造函数的弊端
-
同一个 同样的方法会占据两份内存
解决:
-
提取同一个同样的方法
但提取同一个同样的方法虽然解决了浪费内存的弊端,又造成了
污染全局变量的问题解决:
原型上存放函数
-
原型对象
在构造函数的原型上存放函数
优点
- 解决了同一个
say浪费 内存的问题 - 解决了污染全局变量的问题
解释
- 原型的单词是
prototype, 原型的这个名字是行业内共同认可的名字。 - 原型本质是一个对象,理解为
JavaScript自动帮我们添加的 - 原型是
JavaScript自动帮我们在定义构造函数的时候添加的 所有构造函数的实例,共享一个原型- 原型上一般是挂载函数
原型链继承
用代码的能力实现 面向对象的特性 封装 和 继承
function createStudent(name, age) {
this.name = name;
this.age = age;
}
// 将刚才的全局函数say 直接挂载到 构造函数的原型上 即可
// prototype 是个对象 每一个构造函数都会内置有的. 我们称之为原型
createStudent.prototype.say = function () {
console.log(this.name);
}
const obj = new createStudent("悟能", 83);
const obj1 = new createStudent("悟能1", 84);
console.log(obj.say === obj1.say); // true
// 创建图片
function MyImg(src) {
const img = document.createElement('img');
img.src = src;
document.body.appendChild(img);
this.dom = img;
}
MyImg.prototype.scale = function() {
this.dom.classList.add("scale");
}
const imgModel1 = new MyImg('./images/gotop.png');
const btn1 = document.querySelector(".btn1");
btn1.addEventListener("click", function() {
imgModel1.scale();
})
// 复杂业务 面向对象技术需要(构造函数、this、原型对象)
// 封装 代码 实现以下的功能
// 1 父亲 Element
// 1 属性 dom this.dom
// 2 行为 实例.append(父元素标签的选择器)
// 2 儿子1 ElementDouble div
// 1 继承父亲 Element
// 属性 dom
// 行为 append
// 3 儿子2 ElementSingle img
// 属性 dom
// 行为 append
// 4 当执行以下代码时 出现对应效果
// 1 const divModel = new ElementDouble("div","div内的文字")
// 2 divModel.append("body") ; body标签可以出现 一个 div
// 1 const imgModel = new ElementSingle("img","图片地址")
// 2 imgModel.append("body"); body标签可以出现 一个图片
// 父亲
function Element(tagName) {
const dom = document.createElement(tagName);
this.dom = dom;
}
// 父亲
Element.prototype.append = function(parentSelector) {
document.querySelector(parentSelector).appendChild(this.dom);
};
// 儿子1
function ElementDouble(tagName, content) {
Element.call(this, tagName); // 继承 - 父亲的属性
this.dom.innerText = content;
}
// 去继承父亲的行为
ElementDouble.prototype.append = Element.prototype.append;
// 儿子2
function ElementSingle(tagName, src) {
Element.call(this, tagName);
this.dom.src = src;
}
ElementSingle.prototype.append = Element.prototype.append;
const divModel = new ElementDouble('div', '这个是div');
divModel.append('body');
const imgModel = new ElementSingle('img', './images/b_01.jpg');
imgModel.append('div');
// 如果代码重复实现了 很有可能就是我们要封装的时候
// 以前的封装 仅仅是封装一个小小的函数而已
// 现在的封装, 面相对象的思维来封装
// 封装 属性 父亲
// 封装 方法 父亲
// 两个儿子的代码 有重复部分
// 创建标签重复 this.dom=dom append方法也重复
// 儿子1 有要什么功能
// 先复用父亲的属性和方法
使用结论
- 普通的属性写在构造函数内
- 行为方法写在原型上prototype
Es6 Class
es6的class 的出现 基本上可以替代了es5的构造函数和原型,使之代码结构上更加简洁。
关键字
- class
- 属性
- 方法
- 继承 extends
- 构造函数 constructor
- 方法重写 override:子类方法覆盖父类,super.父类方法()
- 父类的构造函数 super :子类有构造方法且使用this前,必须使用super()
// 方法 constructor 在es6 构造函数
// constructor 会在 new Person的时候触发
class Person {
constructor(name) {
this.name = name;
}
say() {
console.log(this.name + '你好');
}
}
const student = new Person('霉霉');
student.say()
class Person {
// 性能最好 推荐方式!!
say1() {
console.log('say1');
}
say2 = function() {
console.log('s2');
};
say3 = () => {
console.log('s3');
};
}
const p1 = new Person();
const p2 = new Person();
console.log(p1.say1 === p2.say1); // true p1 和 p2 say1方法是同一个-节省内存
console.log(p1.say2 === p2.say2); // false p1 和 p2 say2方法不是同一个-性能不好
console.log(p1.say3 === p2.say3); // false p1 和 p2 say3方法不是同一个-性能不好
// 使用es6的class 实现以下功能
// 1 父类 Element
// 1 属性 this.dom
// 2 方法 append
// 2 定义两个子类 都要继承父亲的属性和方法
// 1 ElementDouble
// 2 ElementSingle
// 3 然后 通过编写代码 把以下程序运行起来
// 父亲
class Element {
constructor(tagName) {
const dom = document.createElement(tagName)
this.dom = dom;
}
append(parentSelector) {
document.querySelector(parentSelector).appendChild(this.dom);
}
}
// 儿子1
// 继承父亲的方法
class ElementDouble extends Element {
constructor(tagName, content) {
super(tagName);
this.dom.innerText = content
}
}
// 儿子2
class ElementSingle extends Element {
constructor(tagName, src) {
super(tagName);
this.dom.src = src
}
}
const divModel = new ElementDouble('div', '这个是div');
divModel.append('body');
const imgModel = new ElementSingle('img', './images/b_01.jpg');
imgModel.append('div');
Es6和箭头函数
// es6 属性的定义 写法有两种
// 1 直接在构造函数内 constructor this.name=name
// 2 可以写在 大括号内
// 3 方法 三种写法
class Person {
// color = 'yellow';
// height = 180;
// weight = 200;
constructor(name) {
this.name = name;
// this.color = 'yellow';
// this.height = 180;
// this.weight = 200;
}
// 写法一
// say() {
// console.log('say 方法被调用了 ' + this.name);
// }
// 写法二
// say = function () {
// console.log('say 方法被调用了 ' + this.name);
// };
// // 写法三
say = () => {
// 箭头函数中的this , 绝大部分指向 window
// 例外 用在 es6的class 充当 方法 this 指向 p1 实例
console.log('say 方法被调用了 ' + this.name);
};
}
const p1 = new Person('悟空');
p1.say();
// console.log(p1);
Class继承
class Person {
constructor(name) {
this.name = name;
}
say() {
console.log('say方法我调用啦 ' + this.name);
}
fly() {
console.log('父亲的起飞');
}
}
// 表示学生要继承父亲
// extends 直接就实现了继承父亲的方法
class Student extends Person {
//
constructor(name, color) {
super(name); // 父亲的构造函数 =es5 Person.call(this,name);
this.color = color;
}
// fly(){
// console.log("儿子 起飞");
// }
// fly = null; // 用儿子的新的null 去覆盖父亲的fly没有父亲的fly
}
const s1 = new Student('学生', 'red');
s1.say();
s1.fly();
/*
Must call super constructor in derived class before accessing 'this' or returning from derived constructor
1 如果你写了 extends 而且还写了 constructor 那你必须要在 constructor 调用了方法 super();
2 如果你只写了 extends 但是你没有写constructor 不需要管super
继承要继承父亲的属性和父亲的行为
1 只实现了继承父亲的行为 还没有实现继承父亲的属性 (super 表示可以继承父亲的属性)
*/
函数参数默认值
- 定义函数的同时,可以给形参一个默认值
- 形参有值输出值,没有值默认值
// es6 函数参数默认值
// 你好 默认值
function show(msg = '你好', str = "你我都好") {
console.log(msg, str);
}
show(); // 没有传递参数 输出默认值你好
show('大家好'); // 输出 大家好
show('大家好', "世界美好"); // 输出 大家好
对象简写
- 如果变量的名字和属性的名字 一致的话,对象可以简写
const obj = {
// 属性名 和 属性值
// username: 123,
};
const username = 123;
const color = 'red';
const say = function() {};
function show() {}
const obj = {
username, // username:username
color, // color : color
say,
show,
height: 100,
};
obj.height = 200;
// 对象中方法的简写
const person = {
show: function() {
console.log("show");
}, // 常规写法
// es6 关于 方法简写
say() {
console.log("say");
}
}
person.show();
person.say();
// 小结:
// 1 变量名如果和你的对象的属性名一致 可以简写
let username='悟空'
const obj = { username }
// 2 对象的方法 简写
const obj ={
say(){ // 简写的方法
}
}
解构
- 提供更加方便获取数组中元素或者对象中属性的写法
数组解构
获取数组中的元素
const [a, b, c, d] = [1, 2, 3, 4];
console.log(a, b, c, d);// 1,2,3,4
元素交互顺序
let a = 1111;
let b = 2222;
[b, a] = [a, b];
console.log(a, b); // 2222 1111
对象解构
获取对象中的属性(重点)
// 以前是 声明两个变量 来获取obj的两个属性
const username = obj.username;
const height = obj.height;
// 现在用对象解构
const { username, height } = obj;
console.log(username, height);
解构加默认值
// 解构 + 默认值
const arr = [1,100];
const [a,b ] = arr; a = 1 b = undefined
const [a, b = 2] = arr;
console.log(a, b); // a =1 b = 2
// b = 2 默认值 (你没有,就使用我,你有,使用你的)
const [a, b = 2] = arr;
console.log(a,b);
const obj = {
username: 100,
height: 500,
};
const {
username,
height = 200
} = obj;
console.log(username, height);
小结:
-
解构对象
const { username,height } = {username:"悟空",height:200}
-
解构数组
在右边找不到对应的数据 就使用默认值
const [a,b]=[100,200]; //c 在右边找不到对应的数据 c 就使用默认值 300 const [a,b,c=300]=[100,200]; -
解构 + 默认值
//如果右边的对象中没有height 属性 那么 height变量 = 1000 const { username,height } = {username:"悟空",height:200} const { username,height=1000 } = {username:"悟空",height:200}
拓展运算符和剩余运算符
拓展运算符
通过 ...符号来获取剩下的参数
把数组或者类数组对象展开成一些列用逗号隔开的值
函数内获取
function show(a, ...all) { // 只能放最后
console.log(a);
console.log(all);
}
show(1);// 1 []
show(1, 2, 3);// 1 [2,3]
数组内展开
function test(a, b, c) {
console.log(a);
console.log(b);
console.log(c);
}
let arr = [1, 2, 3];
test(...arr); //1 2 3
// 将一个数组插入到另一个数据中
let arr1 = [1, 2, 3];
let arr2 = [...arr1, 4, 5, 6];
console.log(arr2); // [1, 2, 3, 4, 5, 6]
// 在数组的后面 新增一个 元素 'd'
const newArr = [...arr,'d'];
//在数组前面 新增一个属性 A
console.log(newArr);
const newArr = ['A', ...arr];
console.log(newArr);
arr.push
arr.unshift
// splice 来实现数组中间插入元素
//获取数组中的最大值
function getMax(...args) {
let max = args[0];
args.forEach((value) => {
if (value > max) {
max = value
}
});
console.log(max);
}
getMax(12, 34, 100, 98, 56, 55)
对象内获取
const obj = {
name:"悟空",
skill:"72变",
say() {}
}
const {name,...others} = obj;
console.log(name); // 悟空
console.log(others); // {skill: "72变", say: ƒ}
对象内展开
// 对象是引用类型 写 = 相当于将obj的地址 给了一份 newObj 两个变量指向的地址同样的 同一个对象
const newObj = obj;
newObj.color = 'yellow';
console.log("新的对象",newObj);
console.log("旧的对象",obj);
// 建议这么做,可以互相影响
const newObj = {...obj,
color: 'yellow'
}; // 给newObj 开辟新的内存空间
//const newObj = { username:"悟空",height:20}; // 给newObj 开辟新的内存空间
newObj.username = '八戒';
newObj.weight = 100;
console.log(obj);
console.log(newObj);
剩余运算符
把逗号隔开的 值序列组合成数组
数组内剩余
// 获取a后面的剩余所有参数
const [a, ...rest] = [1, 2, 3, 4, 5];
console.log(a); // 1
console.log(rest); // [2, 3, 4, 5]
剩余参数和 arguments对象的区别
- 剩余参数只包含那些没有对应形参的实参,而
arguments对象包含了传给函数的所有实参。 arguments对象不是一个真正的数组,而剩余参数是真正的Array实例,也就是说你能够在它上面直接使用所有的数组方法,比如sort,map,forEach或pop。arguments对象还有一些附加的属性 (如callee属性)。
Set对象
- 永远不会有重复元素的对象
- 可以理解为不重复的数组
const set = new Set([1, 5, 3, 4]);
set.add(5);
set.add(5);
console.log(set);
Set对象转为数组
const set = new Set([1, 5, 3, 4]);
set.add(5);
set.add(5);
console.log(set); //
const arr = [...set];// 将set对象转数组
console.log(arr);
const beforeArr = ['ha', 1, 3, 5];
const newArr = new Set(beforeArr); //Set 要new、首字母要大写
newArr.add(6)
const afterArr = [...newArr]; //将set对象转数组
console.log(newArr);
函数的四种调用模式
函数调用模式
如果一个函数不是一个对象的属性时,就是被当做一个函数来进行调用的。此时this指向了window。
function fn(){
console.log(this);// 指向window
}
fn();
方法调用模式
当一个函数被保存为对象的一个属性时,我们称之为一个方法。当一个方法被调用时,this被绑定到当前对象.
const obj = {
sayHi:function(){
console.log(this);//在方法调用模式中,this指向调用当前方法的对象。
}
}
obj.sayHi();
构造函数调用模式
如果函数是通过new关键字进行调用的,此时this被绑定到创建出来的新对象上。
function Person(){
console.log(this);
}
Person();//this指向什么?
var p = new Person();//this指向什么?
方法借用模式
上下文调用模式(借用方法模式)
也叫上下文模式,分为 apply 与 call
call
call方法可以调用一个函数,并且可以指定这个函数的this指向.
const RichWumon = {
name: "富婆",
say: function () {
console.log(this.name, " 我要重金求子");
}
}
const obj = {
name: "帅哥"
}
RichWumon.say(); // 富婆
RichWumon.say.call(obj); // 帅哥
call应用
let divs = document.querySelectorAll('div');
// let divs = document.body.children;
console.log(divs);
function change(nodelist) {
console.log(Object.prototype.toString.call(nodelist));
return Array.prototype.slice.call(nodelist);
}
apply
apply()方法接受的是一个包含多个参数的数组。而call()方法接受的是若干个参数的列表。
const RichWumon = {
name: "富婆",
say: function () {
console.log(this.name, " 我要重金求子");
}
}
const obj = {
name: "帅哥"
}
RichWumon.say(); // 富婆
RichWumon.say.apply(obj); // 帅哥
apply应用
// 简化log方法
function log() {
// 不需要改变this
console.log.apply(console, arguments);
}
bind
**bind()**方法创建一个新的函数, 可以绑定新的函数的this指向
var name = '张三';
function Fn(){
this.age = 1;
console.log(this.name + this.age);
}
Fn(); // 张三 1
// 返回值:新的函数
// 参数:新函数的this指向,当绑定了新函数的this指向后,无论使用何种调用模式,this都不会改变。
let obj = {
name:'小强',
}
const newFn = Fn.bind(obj);
newFn(); // 小强 1
call、apply、bind写法区别
const obj = {
name: '老王',
skill() {
console.log(this.name + '哈哈');
}
}
const person = {
name: '老例',
}
// call方法改变this指向
obj.skill.call(person);
// apply方法改变this指向
obj.skill.apply(person);
// bind方法改变this指向
// 不会直接调用函数,而是返回一个新的函数
let func = obj.skill.bind(person);
// 然后主动调用新的函数-调用skill
func();