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. 两个或者多个参数(括号不能省略)、没有返回值、业务只有一行代码```javascriptconst 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); // falseconsole.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); //trueconsole.log(Object.prototype.__proto__); //nullconsole.log(Object.prototype.hasOwnProperty('hasOwnProperty')); //trueconsole.log(Object.prototype.hasOwnProperty('toString')); //true

数组的原型链
let arr = [12, 23, 15, 32];console.log(arr.__proto__ === Array.prototype); //trueconsole.log(arr.__proto__.__proto__ === Object.prototype); //trueconsole.log(Array.prototype.hasOwnProperty('push')); //trueconsole.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);};// 儿子1function ElementDouble(tagName, content) {Element.call(this, tagName); // 继承 - 父亲的属性this.dom.innerText = content;}// 去继承父亲的行为ElementDouble.prototype.append = Element.prototype.append;// 儿子2function 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); //Objectlet b = new String('牛奶');console.log(b); // String {"牛奶"}console.log(typeof b); // Objectlet c = new Boolean(true);console.log(c); // Boolean {true}console.log(typeof c); // Objectconsole.log(1 + a); // 124console.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}}// 儿子2class 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 constructor1 如果你写了 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:usernamecolor, // color : colorsay,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 undefinedconst [a, b] = [undefined, undefined]; // undefined undefinedconst [a = 1, b = 2] = []; // 1 2console.log(a, b);// 默认值的生效条件, 只有当一个数组成员严格等于( === ) undefined时, 对应的默认值才生效const [a = 1, b = 2] = [3, 0]; //3 0const [a = 1, b = 2] = [3, null]; //3 nullconst [a = 1, b = 2] = [3]; // 3 2console.log(a, b);// 默认值表达式// 如果默认值是表达式,默认值是惰性求值的const func = () => {console.log('我被执行了');return 2}// const [x = func()] = [1]; // 1const [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)); //2console.log(add([])); //0
解构加默认值
// 解构 + 默认值const arr = [1,100];const [a,b ] = arr; a = 1 b = undefinedconst [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 就使用默认值 300const [a,b,c=300]=[100,200];
- 解构 + 默认值
//如果右边的对象中没有height 属性 那么 height变量 = 1000const { 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'];//在数组前面 新增一个属性 Aconsole.log(newArr);const newArr = ['A', ...arr];console.log(newArr);arr.pusharr.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); // 1console.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() {// 不需要改变thisconsole.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 2obj.skill.apply(person, [1, 2]); // 数组形式来传参 // 大郎 1 2const 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);// 然后主动调用新的函数-调用skillfunc();
