js部分面试题

119 阅读22分钟

一、数据类型

  • 基本数据类型:String,Boolean,Number,Undefined, Null(返回的一个空对象)Symbol(ES6新增)

  • 引用数据类型:Object(Array,Function,Date)

  • 区别:基本类型数据的变量值直接存在栈中,而引用数据类型的变量,其值存在堆中,需要通过存储在栈中的地址访问堆中数值(js没有堆栈的概念,但是为了更好理解代码的一些执行方式,所有才有的)

2. 数据类型检测的方式有哪些

(1)typeof

console.log(typeof 2);               // number
console.log(typeof true);            // boolean
console.log(typeof 'str');           // string
console.log(typeof []);              // object    
console.log(typeof function(){});    // function
console.log(typeof {});              // object
console.log(typeof undefined);       // undefined
console.log(typeof null);            // object

其中数组、对象、null都会被判断为object

(2)instanceof

instanceof可以正确判断对象的类型,其内部运行机制是判断在其原型链中能否找到该类型的原型

console.log(2 instanceof Number);                    // false
console.log(true instanceof Boolean);                // false 
console.log('str' instanceof String);                // false 
 
console.log([] instanceof Array);                    // true
console.log(function(){} instanceof Function);       // true
console.log({} instanceof Object);                   // true
复制代码

(3) constructor

console.log((2).constructor === Number); // true
console.log((true).constructor === Boolean); // true
console.log(('str').constructor === String); // true
console.log(([]).constructor === Array); // true
console.log((function() {}).constructor === Function); // true
console.log(({}).constructor === Object); // true

constructor有两个作用,一是判断数据的类型,二是对象实例通过 constrcutor 对象访问它的构造函数。需要注意,如果创建一个对象来改变它的原型,constructor就不能用来判断数据类型了:

function Fn(){};
 
Fn.prototype = new Array();
 
var f = new Fn();
 
console.log(f.constructor===Fn);    // false
console.log(f.constructor===Array); // true

3. 判断数组的方式有哪些

  • 通过instanceof做判断
obj instanceof Array
  • 通过ES6的Array.isArray()做判断
Array.isArrray([1,3]) // true;

4. null和undefined区别

Undefined 和 Null 都是基本数据类型 undefined 代表的含义是未定义,null 代表的含义是空对象。一般变量声明了但还没有定义的时候会返回 undefined,null主要用于赋值给一些可能会返回对象的变量,作为初始化。

5.为什么0.1+0.2 ! == 0.3

6. intanceof 操作符的实现原理及实现

instanceof 运算符用于判断构造函数的 prototype 属性是否出现在对象的原型链中的任何位置。

7. isNaN 和 Number.isNaN 函数的区别?

  • 函数 isNaN 接收参数后,会尝试将这个参数转换为数值,任何不能被转换为数值的的值都会返回 true,因此非数字值传入也会返回 true ,会影响 NaN 的判断。
  • 函数 Number.isNaN 会首先判断传入参数是否为数字,如果是数字再继续判断是否为 NaN ,不会进行数据类型的转换,这种方法对于 NaN 的判断更为准确。

8. == 操作符的强制类型转换规则?

对于 == 来说,如果对比双方的类型不一样,就会进行类型转换 就会进行如下判断流程:

  1. 首先会判断两者类型是否**相同,**相同的话就比较两者的大小;
  2. 类型不相同的话,就会进行类型转换;
  3. 会先判断是否在对比 nullundefined,是的话就会返回 true
  4. 判断两者类型是否为 stringnumber,是的话就会将字符串转换为 number
  5. 判断其中一方是否为 boolean,是的话就会把 boolean 转为 number 再进行判断

9.字符串的转换

1.toSting() // num.toString()

2.隐式转换(加号拼接字符串)

10.数字型的转换

1.parseInt(String)函数 parserFloat(string)函数 2.Number()强制转换成函数

11.布尔型的转换

Boolean()函数

代表空、否定的值转换为false,如'',0,NaN,null,undefined console.log(Boolean(NaN)) // false

12.NaN undefined

如果一个变量声明未赋值 就是 undefined 未定义数据类型

undefined 和数字相加 最后的结果是 NaN

13.短路运算&& ||

  • 对于 || 来说,如果条件判断结果为 true 就返回第一个操作数的值,如果为 false 就返回第二个操作数的值。
  • && 则相反,如果条件判断结果为 true 就返回第二个操作数的值,如果为 false 就返回第一个操作数的值。

二、ES6

(1)块级作用域: 块作用域由 { }包括,let和const具有块级作用域,var不存在块级作用域。块级作用域解决了ES5中的两个问题:

  • 内层变量可能覆盖外层变量
  • 用来计数的循环变量泄露为全局变量

(2)变量提升: var存在变量提升,let和const不存在变量提升,即在变量只能在声明之后使用,否在会报错。

(3)给全局添加属性: 浏览器的全局对象是window,Node的全局对象是global。var声明的变量为全局变量,并且会将该变量添加为全局对象的属性,但是let和const不会。

(4)重复声明: var声明变量时,可以重复声明变量,后声明的同名变量会覆盖之前声明的变量。const和let不允许重复声明变量。

(5)暂时性死区: 在使用let、const命令声明变量之前,该变量都是不可用的。这在语法上,称为暂时性死区。使用var声明的变量不存在暂时性死区。

(6)初始值设置: 在变量声明时,var 和 let 可以不用设置初始值。而const声明变量必须设置初始值。

2. const对象的属性可以修改吗

const保证的并不是变量的值不能改动,而是变量指向的那个内存地址不能改动。对于基本类型的数据(数值、字符串、布尔值),其值就保存在变量指向的那个内存地址,因此等同于常量。

但对于引用类型的数据(主要是对象和数组)来说,变量指向数据的内存地址,保存的只是一个指针,const只能保证这个指针是固定不变的,至于它指向的数据结构是不是可变的,就完全不能控制了。

3. 如果new一个箭头函数的会怎么样

箭头函数是ES6中的提出来的,它没有prototype,也没有自己的this指向,更不可以使用arguments参数,所以不能New一个箭头函数。

new操作符的实现步骤如下:

  1. 创建一个对象
  2. 将构造函数的作用域赋给新对象(也就是将对象的__proto__属性指向构造函数的prototype属性)
  3. 指向构造函数中的代码,构造函数中的this指向该对象(也就是为这个对象添加属性和方法)
  4. 返回新的对象

所以,上面的第二、三步,箭头函数都是没有办法执行的。

4. 箭头函数与普通函数的区别

(1)箭头函数比普通函数更加简洁

  • 如果没有参数,就直接写一个空括号即可
  • 如果只有一个参数,可以省去参数的括号
  • 如果有多个参数,用逗号分割
  • 如果函数体的返回值只有一句,可以省略大括号 (2)箭头函数没有自己的this

(3)箭头函数没有自己的arguments

(4)箭头函数没有prototype

5. 箭头函数的this指向哪⾥?

箭头函数不绑定this 箭头函数没有自己的this关键字

  • 如果在箭头函数中使用this
  • 总是指向最近的外层作用域中的this所指对象
function fn() {
			console.log(this); // obj
			return () => {
				console.log(this); // 最外层作用域fn fn指向obj 即this指向obj
			}
		}
		const obj = { name: 'zhangsan' };
		const resFn = fn.call(obj);
		resFn();

6. 对rest参数的理解

 // 剩余参数语法允许我们将一个不定数量的参数表示为一个数组。
        const sum = (...args) => { // args形参 数组
            let total = 0;
            args.forEach(item => total += item);
            return total;
        };
        console.log(sum(10, 20)); // 30

        // 剩余参数和解构配合使用
        let ary1 = ['张三', '李四', '王五'];
        let [s1, ...s2] = ary1;
        console.log(s1); // '张三'
        console.log(s2); // ['李四', '王五']

7. 扩展运算符

  • 扩展运算符可以将数组或者对象转为用逗号分隔的参数序列

对象的扩展运算符(...)用于取出参数对象中的所有可遍历属性,拷贝到当前对象之中。

(2)数组扩展运算符 数组的扩展运算符可以将一个数组转为用逗号分隔的参数序列,且每次只能展开一层数组。

console.log(...[1, 2, 3])
// 1 2 3
console.log(...[1, [2, 3, 4], 5])
// 1 [2, 3, 4] 5
  • 将数组转换为参数序列
 let ary = ["a", "b", "c"];
        // ...ary // "a", "b", "c"
        console.log(...ary); // a b c
  • 复制数组
const arr1 = [1, 2];
const arr2 = [...arr1];

要记住:扩展运算符(…)用于取出参数对象中的所有可遍历属性,拷贝到当前对象之中,这里参数对象是个数组,数组里面的所有对象都是基础数据类型,将所有基础数据类型重新拷贝到新的数组中。

  • 合并数组

如果想在数组内合并数组,可以这样:

   let ary1 = [1, 2, 3];
        let ary2 = [4, 5, 6];
        // // ...ary1 // 1, 2, 3
        // // ...ary1 // 4, 5, 6
        let ary3 = [...ary1, ...ary2];
        console.log(ary3); // [1, 2, 3, 4, 5, 6]

        // 合并数组的第二种方法
        let ary4 = [1, 2, 3];
        let ary5 = [4, 5, 6];
        ary4.push(...ary5);
        console.log(ary4);
  • 利用扩展运算符将伪数组转换为真正的数组

8. 对象与数组的解构的理解

  • 数组解构:
// 数组解构允许我们按照一一对应的关系从数组中提取值 然后将值赋值给变量
		let ary = [1, 2];
		let [a, b, c] = ary;
		console.log(a); // 1
		console.log(b); // 2
		// 如果解构不成功,变量的值为undefined
		console.log(c); // undefined
  • 对象解构
	// 对象解构允许我们使用变量的名字匹配对象的属性 匹配成功 将对象属性的值赋值给变量
		let person = { name: 'lisi', age: 30, sex: '男' };
		// let { name, age, sex } = person;
		// console.log(name)
		// console.log(age)
		// console.log(sex)
		let { name: myName } = person; // myname属于别名
		console.log(myName); // lisi

9. ES6中模板语法与字符串处理

   // 1. ES6新增的创建字符串的方式,使用反引号定义

        // 模板字符串中可以解析变量
        let name = `鬼脚七`;
        let sayhello = `hello,myname is ${name}`;
        console.log(sayhello);
        let result = {
            name: '酒神',
            age: 34
        };
        // 2. 模板字符串中可以换行
        let html = `
        <div>
            <span>${result.name}</span>
            <span>${result.age}</span>
        </div>
        `;
        console.log(html);
        // 3. 在模板字符串中可以调用函数
        const fn = () => {
            return '我是fn函数';
        };
        html = `我是模板字符串 ${fn()}`;
        console.log(html);

三、JavaScript基础

1. new操作符的实现原理

new操作符的执行过程:

(1)首先创建了一个新的空对象

(2)设置原型,将对象的原型设置为函数的 prototype 对象。

(3)让函数的 this 指向这个对象,执行构造函数的代码(为这个新对象添加属性)

(4)判断函数的返回值类型,如果是值类型,返回创建的对象。如果是引用类型,就返回这个引用类型的对象。

2. map和Object的区别

MapObject
意外的键Map默认情况不包含任何键,只包含显式插入的键。Object 有一个原型, 原型链上的键名有可能和自己在对象上的设置的键名产生冲突。
键的类型Map的键可以是任意值,包括函数、对象或任意基本类型。Object 的键必须是 String 或是Symbol。
键的顺序Map 中的 key 是有序的。因此,当迭代的时候, Map 对象以插入的顺序返回键值。Object 的键是无序的
SizeMap 的键值对个数可以轻易地通过size 属性获取Object 的键值对个数只能手动计算
迭代Map 是 iterable 的,所以可以直接被迭代。迭代Object需要以某种方式获取它的键然后才能迭代。
性能在频繁增删键值对的场景下表现更好。在频繁添加和删除键值对的场景下未作出优化。

3. map和weakMap的区别

  • Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。

  • WeakMap 结构与 Map 结构类似,也是用于生成键值对的集合。但是 WeakMap 只接受对象作为键名( null 除外),不接受其他类型的值作为键名。而且 WeakMap 的键名所指向的对象,不计入垃圾回收机制。

5. 常用的正则表达式有哪些?

// (1)匹配 16 进制颜色值
var regex = /#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})/g;

// (2)匹配日期,如 yyyy-mm-dd 格式
var regex = /^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$/;

// (3)匹配 qq 号
var regex = /^[1-9][0-9]{4,10}$/g;

// (4)手机号码正则
var regex = /^1[34578]\d{9}$/g;

// (5)用户名正则
var regex = /^[a-zA-Z$][a-zA-Z0-9_$]{4,16}$/;

对JSON的理解

JSON 是一种基于文本的轻量级的数据交换格式。它可以被任何的编程语言读取和作为数据格式来传递。 在 js 中提供了两个函数来实现 js 数据结构和 JSON 格式的转换处理

  • JSON.stringify 函数

在前端向后端发送数据时,可以调用这个函数将数据对象转化为 JSON 格式的字符串。

  • JSON.parse() 函数 当从后端接收到 JSON 格式的字符串时,可以通过这个方法来将其解析为一个 js 数据结构,以此来进行数据的访问。

7. JavaScript脚本延迟加载的方式有哪些?

  • 动态创建 DOM 方式: 动态创建 DOM 标签的方式,可以对文档的加载事件进行监听,当文档加载完成后再动态的创建 script 标签来引入 js 脚本。

  • 使用 setTimeout 延迟方法: 设置一个定时器来延迟加载js脚本文件

  • 让 JS 最后加载: 将 js 脚本放在文档的底部,来使 js 脚本尽可能的在最后来加载执行。

  • defer 属性

8. JavaScript 类(伪)数组对象的定义?

一个拥有 length 属性和若干索引属性的对象就可以被称为类数组对象,类数组对象和数组类似,但是不能调用数组的方法

通过 Array.from() 方法来实现转换数组

9. 数组有哪些原生方法?

  • 数组和字符串的转换方法:toString()、toLocalString()、join() 其中 join() 方法可以指定转换为字符串时的分隔符。
  • 数组尾部操作的方法 pop() 和 push(),push 方法可以传入多个参数。
  • 数组首部操作的方法 shift() 和 unshift() 重排序的方法 reverse() 和 sort(),sort() 方法可以传入一个函数来进行比较,传入前后两个值,如果返回值为正数,则交换两个参数的位置。
  • 数组连接的方法 concat() ,返回的是拼接好的数组,不影响原数组。
  • 数组截取办法 slice(),用于截取数组中的一部分返回,不影响原数组。
  • 数组插入方法 splice(),影响原数组查找特定项的索引的方法,indexOf() 和 lastIndexOf() 迭代方法 every()、some()、filter()、map() 和 forEach() 方法
  • 数组归并方法 reduce() 和 reduceRight() 方法

12. 为什么函数的 arguments 参数是类数组而不是数组?如何遍历类数组?

arguments是一个对象,它的属性是从 0 开始依次递增的数字,还有calleelength等属性,与数组相似;但是它却没有数组常见的方法属性,如forEach, reduce等,所以叫它们类数组。

使用Array.from方法将类数组转化成数组

13. 什么是 DOM 和 BOM?

  • DOM 指的是文档对象模型,它指的是把文档当做一个对象,这个对象主要定义了处理网页内容的方法和接口。
  • BOM 指的是浏览器对象模型,它指的是把浏览器当做一个对象来对待,这个对象主要定义了与浏览器进行交互的法和接口。BOM的核心是 window,而 window 对象具有双重角色,它既是通过 js 访问浏览器窗口的一个接口,又是一个 Global(全局)对象。这意味着在网页中定义的任何对象,变量和函数,都作为全局对象的一个属性或者方法存在。window 对象含有 location 对象、navigator 对象、screen 对象等子对象,并且 DOM 的最根本的对象 document 对象也是 BOM 的 window 对象的子对象。

17. JavaScript为什么要进行变量提升,它导致了什么问题?

  • 解析和预编译过程中的声明提升可以提高性能,让函数可以在执行时预先为变量分配栈空间
  • 声明提升还可以提高JS代码的容错性,使一些不规范的代码也可以正常执行

变量提升虽然有一些优点,但是他也会造成一定的问题,在ES6中提出了let、const来定义变量,它们就没有变量提升的机制。

var tmp = 'hello world';

for (var i = 0; i < tmp.length; i++) {
	console.log(tmp[i]);
}

console.log(i); // 11

由于遍历时定义的i会变量提升成为一个全局变量,在函数结束之后不会被销毁,所以打印出来11。

19. ES6模块与CommonJS模块有什么异同?

ES6 Module和CommonJS模块的区别:

  • CommonJS是对模块的浅拷⻉,ES6 Module是对模块的引⽤

20. 常见的DOM操作有哪些

1)DOM 节点的获取(document.querySelector)

2)DOM 节点的创建(createElement)

3)DOM 节点的添加(appendChild)

4)DOM 节点的删除(removeChild)

5)修改 DOM 元素(src,href,innerHtml)

use strict是什么意思 ? 使用它区别是什么?

  • 我们的变量名必须先声明再使用

  • 我们不能随意删除已经声明好的变量

  • 严格模式下全局作用域中函数中的 this 是 undefined。

  • 严格模式下函数里面的参数不允许有重名

  • 严格模式下,如果 构造函数不加new调用, this 指向的是undefined 如果给他赋值则 会报错.

数组的遍历方法有哪些

方法是否改变原数组特点
forEach()数组方法,不改变原数组,没有返回值
map()数组方法,不改变原数组,有返回值,可链式调用
filter()数组方法,过滤数组,返回包含符合条件的元素的数组,可链式调用

29. forEach和map方法有什么区别

这方法都是用来遍历数组的,两者区别如下:

  • forEach()方法会针对每一个元素执行提供的函数,对数据的操作会改变原数组,该方法没有返回值;
  • map()方法不会改变原数组的值,返回一个新数组,新数组中的值为原数组调用函数处理之后的值;

forEach和some区别

   var arr = ['red', 'green', 'blue', 'pink'];
        // 1. forEach迭代 遍历
        // arr.forEach(function(value) {
        //     if (value == 'green') {
        //         console.log('找到了该元素');
        //         return true; // 在forEach 里面 return 不会终止迭代
        //     }
        //     console.log(11);

        // })
        // 如果查询数组中唯一的元素, 用some方法更合适,
        arr.some(function(value) {
            if (value == 'green') {
                console.log('找到了该元素');
                return true; //  在some 里面 遇到 return true 就是终止遍历 迭代效率更高
            }
            console.log(11);

        });

四、原型与原型链

1. 对原型、原型链的理解

在JavaScript中是使用构造函数来新建一个对象的,每一个构造函数的内部都有一个 prototype 属性,它的属性值是一个对象,我们也称为原型对象,这个对象包含了可以由该构造函数的所有实例共享的属性和方法。

对象都会有一个属性 _ _ proto _ _ 指向构造函数的 prototype 原型对象,之所以我们对象可以使用构造函数 prototype 原型对象的属性和方法,就是因为对象有 _ _ proto _ _ 原型的存在。

对象原型( _ _ proto _ _ )和构造函数(prototype)原型对象里面都有一个属性 constructor 属性 ,constructor 我们称为构造函数,因为它指回构造函数本身。

2. 原型修改、重写(手动的利用constructor指回原来的构造函数)

 function Star(uname, age) {
            this.uname = uname;
            this.age = age;
        }
        // 很多情况下,我们需要手动的利用constructor 这个属性指回 原来的构造函数
        // Star.prototype.sing = function() {
        //     console.log('我会唱歌');
        // };
        // Star.prototype.movie = function() {
        //     console.log('我会演电影');
        // }
        Star.prototype = {
            // 如果我们修改了原来的原型对象,给原型对象赋值的是一个对象,则必须手动的利用constructor指回原来的构造函数
            constructor: Star,
            sing: function () {
                console.log('我会唱歌');
            },
            movie: function () {
                console.log('我会演电影');
            }
        }
        var ldh = new Star('刘德华', 18);
        var zxy = new Star('张学友', 19);
        console.log(Star.prototype);
        console.log(ldh.__proto__);
        console.log(Star.prototype.constructor);
        console.log(ldh.__proto__.constructor);

4. 原型链的终点是什么

原型链的终点是null

image.png

五、闭包/递归/深浅拷贝/作用域链/执行上下文

1. 对闭包的理解

闭包是指有权访问另一个函数作用域中变量的函数

简单理解就是 ,一个作用域可以访问另外一个函数内部的局部变量

  • 闭包的作用就是:延伸了变量的作用范围

2. 对递归的了解

如果一个函数在内部可以调用其本身,那么这个函数就是递归函数。

简单理解:函数内部自己调用自己, 这个函数就是递归函数 ,递归函数的作用和循环效果一样 由于递归很容易发生“栈溢出”错误(stack overflow),所以必须要加退出条件 return

3. 对浅拷贝与深拷贝的理解

浅拷贝只是拷贝一层, 更深层次对象级别的只拷贝引用. 深拷贝拷贝多层, 每一级别的数据都会拷贝. Object.assign(target, ...sources) es6 新增方法可以浅拷贝

  • 浅拷贝改变拷贝后对象的基本数据类型值,原拷贝对象不受影响,但是如果改变拷贝后对象的更深层次,引用类型,原拷贝会受到影响。
 // 浅拷贝只是拷贝一层, 更深层次对象级别的只拷贝引用.
        // 深拷贝拷贝多层, 每一级别的数据都会拷贝.
        var obj = {
            id: 1,
            name: 'andy',
            msg: {
                age: 18
            }
        };
        var o = {};
        // for (var k in obj) {
        //     // k 是属性名   obj[k] 属性值
        //     o[k] = obj[k];
        // }
        // console.log(o);
        // o.msg.age = 20;
        // console.log(obj);
        Object.assign(o, obj);
        console.log(o);
        o.msg.age = 20;
        console.log(obj); // 改变了原拷贝的age属性值了,地址发生了改变
  • 深拷贝
// 深拷贝拷贝多层, 每一级别的数据都会拷贝.
        var obj = {
            id: 1,
            name: 'andy',
            msg: {
                age: 18
            },
            color: ['pink', 'red']
        };
        var o = {};
        // 封装函数 
        function deepCopy(newobj, oldobj) {
            for (var k in oldobj) {
                // 判断我们的属性值属于那种数据类型
                // 1. 获取属性值  oldobj[k]
                var item = oldobj[k];
                // 2. 判断这个值是否是数组
                if (item instanceof Array) {
                    newobj[k] = [];
                    deepCopy(newobj[k], item)
                } else if (item instanceof Object) {
                    // 3. 判断这个值是否是对象
                    newobj[k] = {};
                    deepCopy(newobj[k], item)
                } else {
                    // 4. 属于简单数据类型
                    newobj[k] = item;
                }

            }
        }
        deepCopy(o, obj);
        console.log(o);
        // var arr = [];
        // console.log(arr instanceof Object);
        o.msg.age = 20;
        console.log(obj);

2. 对作用域、作用域链的理解

1)全局作用域和函数作用域

(1)全局作用域

  • 最外层函数和最外层函数外面定义的变量拥有全局作用域

  • 所有未定义直接赋值的变量自动声明为全局作用域

  • 所有window对象的属性拥有全局作用域 2)函数作用域

  • 函数作用域声明在函数内部的变零,一般只有固定的代码片段可以访问到

  • 作用域是分层的,内层作用域可以访问外层作用域,反之不行

2)块级作用域
  • 使用ES6中新增的let和const指令可以声明块级作用域,块级作用域可以在函数中创建也可以在一个代码块中的创建(由{ }包裹的代码片段)
  • let和const声明的变量不会有变量提升,也不可以重复声明
  • 在循环中比较适合绑定块级作用域,这样就可以把声明的计数器变量限制在循环内部。 作用域链:  在当前作用域中查找所需变量,但是该作用域没有这个变量,那这个变量就是自由变量。如果在自己作用域找不到该变量就去父级作用域查找,依次向上级作用域查找,直到访问到window对象就被终止,这一层层的关系就是作用域链。

3. 对执行上下文的理解

(1)全局执行上下文

任何不在函数内部的都是全局执行上下文,它首先会创建一个全局的window对象,并且设置this的值等于这个全局对象,一个程序中只有一个全局执行上下文。

(2)函数执行上下文

当一个函数被调用时,就会为该函数创建一个新的执行上下文,函数的上下文可以有任意多个。

六、this/call/apply/bind

1. 对this对象的理解

  • 普通函数 this 指向window
  • 对象的方法 this指向的是对象 o
  • 构造函数 this 指向 ldh 这个实例对象 原型对象里面的this 指向的也是 ldh这个实例对象
  • 绑定事件函数 this 指向的是函数的调用者 btn这个按钮对象
  • 定时器函数 this 指向的也是window

2. 对call() 和 apply() 和bind()的理解

call()

  • call 第一个可以调用函数 第二个可以改变函数内的this 指向
  • call 的主要作用可以实现继承 apply()
  • 也是调用函数 第二个可以改变函数内部的this指向
  • 但是他的参数必须是数组(伪数组)
  • apply 的主要应用 比如说我们可以利用 apply 借助于数学内置对象求数组最大值

bind()

  • 不会调用原来的函数 可以改变原来函数内部的this 指向
  • 返回的是原函数改变this之后产生的新函数
  • 如果有的函数我们不需要立即调用,但是又想改变这个函数内部的this指向此时用bind

七、异步编程

1. 异步编程的实现方式?

回调函数 的方式

Promise 的方式

async 函数

2. 对Promise的理解

简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。

4. Promise的基本用法

(1)创建Promise对象

Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。

Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolvereject

const promise = new Promise(function(resolve, reject) {
  // ... some code
  if (/* 异步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});

一般情况下都会使用new Promise()来创建promise对象,