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)对象的方法也不可以使用箭头函数。
(5) 需要this指向调用对象的时候也不能用箭头函数。
箭头函数是匿名函数,一般做为参数传递
- 没有形参、没有返回值
const func = () => {
console.log('执行业务');
};
- 没有形参、没有返回值、业务只有一行代码 花括号都可以省略
如果没有参数,()也不能省略const func = () => console.log('执行业务');
- 只有一个形参、没有返回值、业务只有一行代码(括号能省略) ```javascript 1.const func = num => console.log(num + 1);
2.const func = (num) => console.log(num + 1);
4. 两个或者多个参数(括号不能省略)、没有返回值、业务只有一行代码
```javascript
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}
}
构造函数
- 构造函数的首字母大写 - 行内编码规范
- 构造函数内不要写 return
function Createobj() {
this.name = 'jack';
//返回一个基本数据类型
return undefined
}
构造函数内不要写 return
=> 当你return 一个基本数据类型的时候,写了白写
=> 当你return 一个复杂数据类型的时候,构造函数白写
- 构造函数想要解决 性能问题
- 一定会
把方法-函数写在构造函数的外面
- 再通过this.say 指向外部的函数
- 一定会
构造函数内的this
- 因为函数调用和new关键字连用,
this会指向自动创建出来的那个对象
- 又因为这个对象会被自动返回,所以不需要return
- 构造函数自动创建对象的过程,叫做实例化的过程,管构造函数创建出来的对象,叫做实例对象
- 构造函数内的this指向当前实例
工作原理
- 开辟空间
- 将新的创建的对象指向构造函数中的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方法,很容易就覆盖()
构造函数的弊端
- 同一个 同样的方法会占据两份内存
解决:- 提取同一个同样的方法
但提取同一个同样的方法虽然解决了浪费内存的弊端,又造成了污染全局变量
的问题
解决:原型上存放函数
- 提取同一个同样的方法
原型对象
在构造函数里面
- 每一个函数天生在自带一个属性prototype,是一个对象数据类型
- 构造函数也是一个函数,所以构造函数也有prototype,
这个天生自带的prototype内有一个constrictor的属性。
=>表明我是谁的构造函数
=>指向自己的构造函数 - 原型是一个对象,表明可以向里面添加一些成员
在实例对象里面
- 每一个对象天生自带一个属性, proto ,指向所属构造函数的prototype
- 实例对象也是一个对象,所以实例对象也有 proto
在构造函数的原型上存放函数
优点
- 解决了同一个
say
浪费 内存的问题 - 解决了污染全局变量的问题
解释
- 原型的单词是
prototype
, 原型的这个名字是行业内共同认可的名字。 - 原型本质是一个对象,理解为
JavaScript
自动帮我们添加的 - 原型是
JavaScript
自动帮我们在定义构造函数
的时候添加的 所有构造函数的实例,共享一个原型
- 原型上一般是挂载函数
原型链
原型链的终点
function People(name, age) {
this.name = name;
this.age = age;
}
const xiaoming = new People('小明', 18);
console.log(xiaoming.__proto__.__proto__ === Object.prototype); //true
console.log(Object.prototype.__proto__); //null
console.log(Object.prototype.hasOwnProperty('hasOwnProperty')); //true
console.log(Object.prototype.hasOwnProperty('toString')); //true
数组的原型链
let arr = [12, 23, 15, 32];
console.log(arr.__proto__ === Array.prototype); //true
console.log(arr.__proto__.__proto__ === Object.prototype); //true
console.log(Array.prototype.hasOwnProperty('push')); //true
console.log(Array.prototype.hasOwnProperty('splice')); //true
原型链继承
用代码的能力实现 面向对象的特性 封装 和 继承
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
包装类
- 很多语言都有“包装类”的设计,
包装类的目的就是为了让基本数据类型值可以从它们的构造函数的prototype上获得方法
。 - Number()、String()、和Boolean()分别是数字、字符串、布尔值的
“包装类”
.
let a = new Number(123);
console.log(a); // Number {123}
console.log(typeof a); //Object
let b = new String('牛奶');
console.log(b); // String {"牛奶"}
console.log(typeof b); // Object
let c = new Boolean(true);
console.log(c); // Boolean {true}
console.log(typeof c); // Object
console.log(1 + a); // 124
console.log(b.slice(0, 1)); //牛
console.log(b.slice(0, 2)); //牛奶
let d = 1;
console.log(d.__proto__ == Number.prototype); //true
包装类总结
- Number()、String()、和Boolean()的实例都是object类型,它们的PrimitiveValue属性存储它们的本身的值
- new出来的基本类型值可以正常参与运算
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] = [1, 2, 3];
console.log(a, b, c); // 1 2 3
// 不取的,可以直接用逗号跳过
const [e, [, , f], g] = [1, [2, 4, 5], 3];
console.log(e, f, g); // 1 5 3
解构赋值的默认值
// 默认值的基本用法
const [a, b] = []; //undefined undefined
const [a, b] = [undefined, undefined]; // undefined undefined
const [a = 1, b = 2] = []; // 1 2
console.log(a, b);
// 默认值的生效条件, 只有当一个数组成员严格等于( === ) undefined时, 对应的默认值才生效
const [a = 1, b = 2] = [3, 0]; //3 0
const [a = 1, b = 2] = [3, null]; //3 null
const [a = 1, b = 2] = [3]; // 3 2
console.log(a, b);
// 默认值表达式
// 如果默认值是表达式,默认值是惰性求值的
const func = () => {
console.log('我被执行了');
return 2
}
// const [x = func()] = [1]; // 1
const [x = func()] = []; // 我被执行了
console.log(x); // 2
数组解构
- 分析某一数据的结构,将想要的提取出来,赋值给变量和常量
- 模式(匹配)解构
[] = [1,2,3];
- 索引值相同的完成赋值
const [a,b,c] = [1,2,3];
- 不取的用逗号跳过
const [e, [, , f], g] = [1, [2, 4, 5], 3];
数组解构赋值的原理
// 索引值相同的完成赋值
const [a, b, c] = [1, 2, 3];
console.log(a, b, c); // 1 2 3
// 不取的,可以直接用逗号跳过
const [e, [, , f], g] = [1, [2, 4, 5], 3];
console.log(e, f, g); // 1 5 3
获取数组中的元素
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
对象解构
- 模式(解构)匹配
属性名相同
的完成赋值- 取别名
const { age: age, username: uname } = { username: 'Alex', age: 18 };
console.log(age, uname);
const {sex: sex,name: uname} = {sex: '女',name: '可可'};
console.log(sex, uname);
对象解构赋值的注意事项
- 对象解构赋值的默认值。
对象的属性值严格等于undefined时,对应的默认值才会生效。const {username = 'zhang', age = 0} = { username: 'alex'};
console.log(username, age); //alex 0
- 将一个已经声明的变量用于解构赋值。
如果将一个已经声明的变量用于对象的解构赋值,赋值需在圆括号中 。 ```javascript // let { x } = { x: 1 }; // console.log(x); //报错,因为重复声明
let x = 2;
({ x } = { x: 1 });
console.log(x); //1
3. 对象的解构赋值可以取到继承的属性或方法
<a name="VW4cj"></a>
##### 获取对象中的属性(重点)
```javascript
// 以前是 声明两个变量 来获取obj的两个属性
const username = obj.username;
const height = obj.height;
// 现在用对象解构
const { username, height } = obj;
console.log(username, height);
其他类型解构赋值
- 字符串可以按数组或对象的形式解构赋值
- 字符串可以按数组或对象的形式解构赋值
- undefined 和 null 无法转为对象,解构赋值都会报错
函数参数的解构赋值
// 函数参数的解构赋值
const array = [1, 1];
// const add = arr => arr[0] + arr[1];
const add = ([x = 0, y = 0]) => x + y;
console.log(add(array)); //2
console.log(add([])); //0
解构加默认值
// 解构 + 默认值
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}
展开运算符和剩余运算符
展开运算符和剩余运算符的区别
展开运算符
[3,1,2] —->3,1,2
剩余运算符
3,1,2 —->[3,1,2]
展开运算符
通过
…符号来获取剩下的参数
把数组或者类数组对象展开成一些列用逗号隔开的值
函数内获取
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]
// 计算数组最大值
const arr = [1, 2, 3, 4];
console.log(Math.max(...arr)); //4
剩余参数和 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传参数写法
传递参数
- obj.skill.call(person,参数1,参数2)
- obj.skill.apply(person,[参数1,参数2])
- const func = obj.skill.bind(person);
func(参数1,参数2)
// 传递参数的时候不同写法
const obj = {
name: '老王',
skill(a, b) {
console.log(this.name + ' ' + a + ' ' + b);
},
};
const person = {
name: '大郎',
};
obj.skill.call(person, 1, 2); // 传参 // 大郎 1 2
obj.skill.apply(person, [1, 2]); // 数组形式来传参 // 大郎 1 2
const func = obj.skill.bind(person);
func(1, 2); // 大郎 1 2
// 借用 Math.max方法,但目的不是修改this指向, 而是计算数组最大值
const arr = [1, 2, 3, 4];
const max = Math.max.apply(null, arr); //接受数组
console.log(max); //4
// bind call apply 都可以实现修改this指向
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();