数据类型
1、javascript有哪些数据类型?有什么区别?
-
ES5中分为基础数据类型(Undefined、Null、Boolean、String、Number)和引用数据类型(Object、Array、Function),ES6新增了Symbol、BigInt的数据类型。
其中,Symbol、BigInt的含义:
- Symbol创建之后代表独一无二且不可变的数据类型,本质上是一种唯一标识符,可作为对象的唯一属性名,主要是为了解决全局变量冲突的问题。
- BigInt是一种数字类型的数据,比
Number数据类型支持的范围更大的整数值,使用BigInt,整数溢出不再是问题,另外可以更加安全的使用时间戳,大整数。
-
区别,存储上的不同:
- 基本数据类型直接存储在栈中,占据 空间小、大小固定,属于被频繁使用数据。存取的方式是先进后出。
- 引用数据类型是存储在堆中的对象,占据空间大、大小不固定,引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。堆是一个优先队列,先进先出,堆中的内存一般由开发者分配释放,若开发者不释放,程序结束由垃圾回收机制回收。
2、数据类型检测的方式有哪些?
- typeof 有6种数据结果、其中数组、对象、null都是object,其他判断正确
typeof 1 // number
typeof true // boolean
typeof 'test' // string
typeof function(){} // function
typeof undefined // undefined
typeof [] // object
typeof null // object
typeof {} // object
- instanceof 只能判断引用数据类型,内部机制就是判断在原型链中能否找到该类型的原型。
1 instanceof Number // falsee
[] instanceof Array // true
function(){} instanceof Function // true
{} instanceof Object // true
- constructor
- 判断数据的类型
(1).constructor === Number // true
(true).constructor === boolean; // true
('str').constructor === String; // true
([]).constructor === Array; // true
(function(){}).constructor === Function; // true
({}).constructor === Object // true
2.对象实例通过constructor对象访问它的构造函数
function Fn(){}
Fn.prototype = new Array();
var f = new Fn();
console.log(f.constructor === Fn); // false
console.log(f.constructor === Array); // true
- Object.prototype.toString.call()
console.info(Object.prototype.toString.call(null) === '[object Null]')
console.info(Object.prototype.toString.call(undefined) === '[object Undefined]')
console.info(Object.prototype.toString.call('123') === '[object String]')
console.info(Object.prototype.toString.call(123) === '[object Number]')
console.info(Object.prototype.toString.call(true) === '[object Boolean]') //引用类型
function fn(){
console.log('test');
}
console.info(Object.prototype.toString.call(fn) === '[object Function]');
var date = new Date();
console.info(Object.prototype.toString.call(date) === '[object Date]')
var arr = [1,2,3];
console.info(Object.prototype.toString.call(arr) === '[object Array]')
3、null、undefined的区别?
undefined代表未定义,null代表空对象,一般变量声明了还没有定义的时候会返回undefined,赋给可能返回对象的变量,作为初始化。
typeof null // undefined
null == undefined // true
null === undefined // false
4、Object.is()与比较操作符“==”、“===”的区别?
- 双等号(==)进行相等判断时,如果两边的类型不一致,则会进行强制类型转化后再进行比较。
- 三等号(===)进行相等判断时,如果两边的类型不一致时,不会做强制类型准换,直接返回 false
- 使用 Object.is 来进行相等判断时,一般情况下和三等号的判断相同,它处理了一些特殊的情况,比如 -0 和 +0 不再相等,两个 NaN 是相等的。
JS基础
1、判断数组的方式
const arr = [1,2,3];
Array.isArray(arr);
Object.protorype.toString.call() === 'Array';
arr instanceof Array
Array.prototype.isPrototypeOf(arr);
2、深拷贝、浅拷贝以及各自的方法?
概念:深浅拷贝只针对引用数据类型,浅拷贝只复制指向某个对象的指针,而不是复制对象本身,新旧对象共享一个内存;深拷贝是创造一个一摸一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。
方法:
- 浅拷贝
- 直接赋值
var fruits = ["Banana", "Orange", "Apple", ["Mango", "blue"]];
var obj = fruits;
obj[2] = "water";
obj[3][0] = "water";
console.log("fruits", fruits); // ['Banana', 'Orange', 'water', ['water', 'blue']]
console.log("obj", obj); // ['Banana', 'Orange', 'water', ['water', 'blue']]
2.Object.assign
var fruits = ["Banana", "Orange", "Apple", ["Mango", "blue"]];
var obj = Object.assign([], fruits);
obj[2] = "water";
obj[3][0] = "water";
console.log("fruits", fruits); // ['Banana', 'Orange', 'Apple', ['water', 'blue']]
console.log("obj", obj); // ['Banana', 'Orange', 'water', ['water', 'blue']]
Object.assign()方法是把源对象自身的任意多个的可枚举属性拷贝给目标对象,然后返回给目标对象,然后返回给目标对象,但因为拷贝的是对象的属性的引用,不是对象本身,属于浅拷贝
- ...
var fruits = ["Banana", "Orange", "Apple", ["Mango", "blue"]];
var obj = {...fruits};
obj[2] = "water";
obj[3][0] = "water";
console.log("fruits", fruits);// ['Banana', 'Orange', 'Apple', ['water', 'blue']]
console.log("obj", obj); // ['Banana', 'Orange', 'water', ['water', 'blue']]
- concat()
var fruits = ["Banana", "Orange", "Apple", ["Mango", "blue"]];
var obj = fruits.concat();
obj[2] = "water";
obj[3][0] = "water";
console.log("fruits", fruits);// ['Banana', 'Orange', 'Apple', ['water', 'blue']]
console.log("obj", obj);// ['Banana', 'Orange', 'water', ['water', 'blue']]
- slice()
var fruits = ["Banana", "Orange", "Apple", ["Mango", "blue"]];
var obj = fruits.slice();
obj[2] = "water";
obj[3][0] = "water";
console.log("fruits", fruits);// ['Banana', 'Orange', 'Apple', ['water', 'blue']]
console.log("obj", obj);// ['Banana', 'Orange', 'water', ['water', 'blue']]
注: 2、3、4、5方法都是对第一层属性依次拷贝,如果第一层的属性是基本数据类型,就拷贝值;如果是引用数据类型,就拷贝内存地址。
- 深拷贝 1.JSON.stringify() 和 JSON.parse()
var fruits = ["Banana", "Orange", "Apple", ["Mango", "blue"]];
var obj = JSON.parse(JSON.stringify(fruits))
obj[2] = "water";
obj[3][0] = "water";
console.log("fruits", fruits);// ['Banana', 'Orange', 'Apple', ['Mango', 'blue']]
console.log("obj", obj);// ['Banana', 'Orange', 'water', ['water', 'blue']]
- jquery .extend(true)
var fruits = ["Banana", "Orange", "Apple", ["Mango", "blue"]];
var obj = $.extend(true,{},fruits);
obj[2] = "water";
obj[3][0] = "water";
console.log("fruits", fruits);// ['Banana', 'Orange', 'Apple', ['Mango', 'blue']]
console.log("obj", obj);// ['Banana', 'Orange', 'water', ['water', 'blue']]
- 递归进行拷贝
function deepClone(obj){
var temp = Array.isArray(obj)?[]:{};
for(var key in obj){
temp[key] = typeof obj[key] === 'object' ? deepClone(obj[key]):obj[key];
}
return temp;
}
var fruits = ["Banana", "Orange", "Apple", ["Mango", "blue"]];
var obj = deepClone(fruits);
obj[2] = "water";
obj[3][0] = "water";
console.log("fruits", fruits);// ['Banana', 'Orange', 'Apple', ['Mango', 'blue']]
console.log("obj", obj);// ['Banana', 'Orange', 'water', ['water', 'blue']]
- 推荐
function deepClone(source){
if(!source) return;
var target;
if(JSON){
target = JSON.parse(JSON.stringify(source));
} else if(typeof(source)==='object'){
target = source instanceof(Array) ? [] : {};
for(var key in source){
temp[key] = typeof obj[key] === 'object' ? deepClone(obj[key]):obj[key];
}
} else {
target = source;
}
return target;
}
var fruits = ["Banana", "Orange", "Apple", ["Mango", "blue"]];
var obj = deepClone(fruits);
obj[2] = "water";
obj[3][0] = "water";
console.log("fruits", fruits);// ['Banana', 'Orange', 'Apple', ['Mango', 'blue']]
console.log("obj", obj);// ['Banana', 'Orange', 'water', ['water', 'blue']]
3、数组有哪些原生的方法?
详情查看原生方法
4、数组的遍历方法有哪些?
| 方法 | 是否改变原数组 | 特点 |
|---|---|---|
| forEach() | 否 | 数组方法,不改变原数组,没有返回值,使用foreach遍历,break不能中断循环,使用return不能返回到外层函数 |
| map() | 否 | 数组方法,不改变原数组,有返回值,可链式调用 |
| filter() | 否 | 数组方法,过滤数组,返回符合条件的元素的数组,可链式调用 |
| some()、every() | 否 | 数组方法,some()只要有一个是true,返回true;every()有一个是false,便返回false |
| find()、findIndex() | 否 | 数组方法,find()返回第一个符合条件的值;findIndex()返回第一个符合条件的值的索引值 |
| reduce()、reduceRight() | 否 | 数组方法,reduce()对数组进行正序操作;reduceRight()对数组逆序操作 |
| for...of | 否 | for...of遍历具有迭代器(iterator) 对象的属性,返回的是数组的元素、对象的属性值,不能遍历普通的obj对象 |
5、for...in和for...of的区别?如何用for...of遍历对象?
6、为什么函数的arguments参数是类数组而不是数组?如何遍历类数组?两者的区别
arguments是一个对象,它的属性是从0开始依次递增的数字,还有callee和length等属性,与数组相似;但是没有数组常见的属性和方法,比如forEach、reduce等,所以叫类数组。
遍历类数组:
// 方法一
Array.prototype.forEach.call(arguments,a=>console.log(a))
// 方法二
Array.from(arguments).forEach((a)=>{
console.log(a);
})
// 方法三
[...arguments].forEach(a=>{
console.log(a);
})
// 方法四
Array.prototype.splice.call(arrayLike, 0).forEach(a=>{
console.log(a);
})
// 方法五
Array.prototype.slice.call(arrLike).forEach(a=>{
console.log(a);
})
7、javascript有哪些内置对象?
- 值属性,没有自己的属性和方法,null、NaN、undefined等
- 函数属性,parseInt()、parseFloat()、eval()等
- 基本对象 Object、Function、Boolean、Error等
- 数字和日期对象,Number、Math、Date
- 字符串 String、RegExp
- 可索引的集合对象 Array
- 使用键的集合对象 Map、Set
- 数学计算的单体内置对象 Math
8、什么是DOM、BOM?
js是有三个部分构成的,ECMAScript,DOM和BOM,根据宿主(浏览器)的不同,具体的表现形式也不同,ie和其他的浏览器风格迥异。
- DOM是W3C的标准;[所有浏览器公共遵守的标准]
- BOM是各个浏览器厂商根据DOM在各自浏览器上的实现
- window是BOM对象,而非js对象
DOM(文档对象模型)是 HTML 和 XML 的应用程序接口(API)。DOM将把整个页面规划成由节点层级构成的文档。HTML 或 XML 页面的每个部分都是一个节点的衍生物。
BOM(浏览器对象模型)是对浏览器窗口进行访问和操作,把浏览器当做一个对象来对待,这个对象主要定义了与浏览器进行交互的方法和接口,通常浏览器特定的 JavaScript 扩展都被看做 BOM 的一部分。
扩展包含:
- 弹出新的浏览器窗口
- 移动、关闭浏览器窗口以及调整窗口大小
- 提供 Web 浏览器详细信息的定位对象
- 提供用户屏幕分辨率详细信息的屏幕对象
- 对 cookie 的支持
- IE 扩展了 BOM,加入了 ActiveXObject 类,可以通过 JavaScript 实例化 ActiveX 对象
BOM的核心是 window,而 window 对象具有双重角色,它既是通过 js 访问浏览器窗口的一个接口,又是一个 Global(全局)对象。这意味着在网页中定义的任何对象,变量和函数,都作为全局对象的一个属性或者方法存在。window 对象含有 location 对象、navigator 对象、screen 对象等子对象,并且 DOM 的最根本的对象 document 对象也是 BOM 的 window 对象的子对象。
9、 ES6模块和CommonJS模块有什么不同?
- CommonJS
- 对于基本数据类型,属于复制,即会被模块缓存。
2.对于复杂数据类型,属于浅拷贝。由于两个模块引用的对象指向同一个内存空间,因此对该模块的值做修改会影响另一个模块。
3.当使用require命令加载某个模块时,就会运行整个模块的代码。
4.当使用require命令加载同一个模块时,不会再执行该模块,而是取到缓存之中的值。也就是说,CommonJS模块无论加载多少次,都只会在第一次加载时运行一次,以后再加载,就返回第一次运行的结果。
5.循环加载时,属于加载时执行。即脚本代码在require的时候,就会全部执行。一旦出现某个模块被"循环加载",就只输出已经执行的部分,还未执行的部分不会输出。
-
ES6模块 1.ES6模块中的值属于动态只读引用。
2.对于只读来说,即不允许修改引入变量的值,import的变量是只读的,不论是基本数据类型还是复杂数据类型。当模块遇到import命令时,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的模块去取值。
3.对于动态来说,原始值发生变化,import加载的值也会发生变化。不论是基本数据类型还是复杂数据类型。
4.循环加载时,ES6模块是动态引用,只要模块之间存在引用,代码就能够执行。 -
CommonJS
// b.js
let count = 1
let plusCount = () => {
count++
}
setTimeout(() => {
console.log('b.js-1', count)
}, 1000)
module.exports = {
count,
plusCount
}
// a.js
let mod = require('./b.js')
console.log('a.js-1', mod.count)
mod.plusCount()
console.log('a.js-2', mod.count)
setTimeout(() => {
mod.count = 3
console.log('a.js-3', mod.count)
}, 2000)
node a.js
a.js-1 1
a.js-2 1
b.js-1 2 // 1秒后
a.js-3 3 // 2秒后
- ES6
// b.js
import {foo} from './a.js';
export function bar() {
console.log('bar');
if (Math.random() > 0.5) {
foo();
}
}
// a.js
import {bar} from './b.js';
export function foo() {
console.log('foo');
bar();
console.log('执行完毕');
}
foo();
babel-node a.js
foo
bar
执行完毕
// 执行结果也有可能是
foo
bar
foo
bar
执行完毕
执行完毕
10、常见的DOM操作有哪些?
- DOM 节点的获取
getElementById // id查询
getElementByTagName // 按照标签名查询
getElementByClassName // 按照类名查询
querySelectorAll // 按照 css 选择器查询
var idDom = document.getElementById('idDom');
var pList = document.getElementByTagName('p'); // 查询标签为p的集合
console.info(pList.length);
console.info(pList[0]);
var moocDom = document.getElementByClassName('mooc'); // 查询类名为mooc的
var moocList = document.querySelectorAll('.mooc'); // 查询类名为 mooc 的集合
- DOM 节点的创建 创建一个新节点,并把它添加到 指定节点的后面。已知的HTML结构如下:
<html>
<head>
<title>DEMO<title>
</head>
<body>
<div id="contaioner">
<h1 id="title">我是标题</h1>
</div>
</body>
</html>
要求添加一个有内容的span节点到id为title的节点后面
var container = document.getElementById('container');
var targetSpan = document.createElement('span');
// 设置 span 节点的内容
targetSpan.innerHTML = 'hello world'
// 把新创建的元素塞进父节点里去
container.appendChild(targetSpan)
- DOM 节点的删除结构 删除指定的DOM节点。已知的HTML结构如下:
<html>
<head>
<title>DEMO<title>
</head>
<body>
<div id="contaioner">
<h1 id="title">我是标题</h1>
</div>
</body>
</html>
要求添加一个有内容的span节点到id为title的节点后面
// 获取目标元素的父元素
var container = document.getElementById('container')
// 获取目标元素
var targetNode = document.getElementById('title')
// 删除目标元素
container.removeChild(targetNode)
// 获取目标元素的父元素
var container = document.getElementById('container')
// 获取目标元素
var targetNode = container.childNodes[1]
// 删除目标元素
container.removeChild(targetNode)
- DOM 节点的修改 修改 DOM 元素这个动作可以分很多维度,比如说移动 DOM 元素的位置,修改 DOM 元素的属性等.
将指定的两个 DOM 元素交换位置, 已知的 HTML 结构如下:
<html>
<head>
<title>DEMO</title>
</head>
<body>
<div id="container">
<h1 id="title">我是标题</h1>
<p id="content">我是内容</p>
</div>
</body>
</html>
需要调换 title 和 content 的位置,可以考虑 insertBefore 或者 appendChild
// 获取父元素
var container = document.getElementById('container')
// 获取两个需要被交换的元素
var title = document.getElementById('title')
var content = document.getElementById('content')
// 交换两个元素,把 content 置于 title 前面
container.insertBefore(content, title)
11、常用的正则表达式有哪些
// (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}$/;
12、use strict是什么意思?使用它区别是什么?
use strict 是es5添加的运行模式,使js在严格的条件下运行,目的在于:
1.消除js语法的不合理、不严谨之处,减少怪异行为;
2.消除代码的不安全之处,保证代码运行的安全;
3.提高编译器效率,增加运行速度;
4.为未来新版本的js做好铺垫
区别:
1.禁止使用with语句
2.禁止this关键字指向全局对象
3.对象不能有重名的属性
13、如何判断一个对象是否属于某个类?
- instanceof
var b = new Date();
b instanceof Array // false
b instanceof Date // true
function Demo(name,age){
this.name = name;
this.age = age;
}
var demo_1 = new Demo("1023",25);
console.log(demo_1 instanceof Demo); //true
console.log(demo_1 instanceof Object);//true
instanceof不但可以判断出是直接类的实例(通过new的方式),还可以判断是否是父类的实例
- constructor
function Demo(name,age){
this.name = name;
this.age = age;
}
var demo_1 = new Demo("1023",25);
console.log(demo_1.constructor == Demo);//true
console.log(demo_1.constructor == Object);//false
constructor属性只可以判断出是否是直接类的实例,不可以判断父类的实例
- Object.prototype.toString.call
function Demo(name,age){
this.name = name;
this.age = age;
}
var demo_1 = new Demo("1023",25);
Object.prototype.toString.call(demo_1) === '[object Object]' // true
14、 forEach 和 map方法的区别?
这些方法是用来遍历数组的,两者的区别如下:
- forEach方法会针对每一个元素执行提供的函数,对数据的操作会改变原数组,该方法没有返回值;
- map方法不会改变数组的值,返回一个新数组,数组中的值为原数组调用函数处理之后的值;
15、 escape、encodeURI、encodeURIComponent 的区别?
- encodeURI 是对整个 URI 进行转义,将 URI 中的非法字符转换为合法字符,所以对于一些在 URI 中有特殊意义的字符不会进行转义。
- encodeURIComponent 是对 URI 的组成部分进行转义,所以一些特殊字符也会得到转义。
- escape 和 encodeURI 的作用相同,不过它们对于 unicode 编码为 0xff 之外字符的时候会有区别,escape 是直接在字符的 unicode 编码前加上 %u,而 encodeURI 首先会将字符转换为 UTF-8 的格式,再在每个字节前加上 %。
ES6
1、let、const、var的区别
(1)块级作用域: 块作用域由{}包括,let和const具有块级作用域,var不存在块级作用域。
块级作用域解决ES5两个问题:
内层变量可能覆盖外层变量;
用来计数的循环变量泄露为全局变量;
(2)变量提升: var存在变量提升,let和const不存在变量提升,即变量只能在声明之后使用,否则会报错。
(3)全局添加属性:浏览器的全局对象是window,Node的全局对象是global。var声明的变量为全局变量,并且会 将该变量添加为全局对象的属性,const和let不允许。
(4)重复声明:var可以重复声明变量,后声明的同名变量会覆盖之前声明的变量,const和let不允许重复声明变量。
(5) 暂时性死区:在let、const声明变量之前,该变量都是不可用的,我们称之为暂时性死区,var不会存在。
(6) 初始值的设置:const声明变量必须设置初始值,let、var不用。
(7) 指针指向:const不允许改变指针的指向,let和const都可以更改(重新赋值)。
2、const对象的属性是否可以修改?
const保证的不是变量的值不动,而是变量指向的那个内存地址不能改动。对于基本数据类型,就是常量,不能改动。对于引用数据类型来说,变量指向的是内存的数据地址,保存的只是一个指针(栈中),只要指针不变,它指向的数据(存在堆中)结构变不变不受控制。
3、箭头函数和普通函数的区别?new一个箭头函数可以吗?
(1)箭头函数更简洁
(2)箭头函数没有自己的this
(3)call、apply、bind不能改变箭头函数this的指向
(4)箭头函数不能被new调用,不能作为构造函数使用
(5)箭头函数没有自己的arguments
(6)箭头函数没有prototype
注:箭头函数没有自己的this,它所谓的this是捕获其所在上下文的this值,并没有自己的this,不能被new调用,也不会被改变。
4、对对象和数组的解构的理解?
解构是ES6提供的一种新的提取数据的模式,这种模式能够从对象或数组里 有针对性地拿到想要的数值。
- 数组的解构
const [a,b,c] = [1,2,3] // a 1 b 2 c 3
const [a,,c] = [1,2,3] // a 1 c 3
- 对象的解构
const stu = { name: 'Bob', age: 24 }
const { name, age } = stu; // 'Bob' 24
// 对象解构严格以属性名作为定位依据,所以就算调换了 name 和 age 的位置,结果也是一样的
const { age, name } = stu; // 24 'Bob'
原型、原型链
1、对原型、原型链的理解?
详情见 原型、原型链、继承
闭包、作用域、执行上下文
1、对闭包的理解?
- 什么是闭包?
函数执行后返回的结果是一个内部函数,并被外部变量所引用,如果内部函数持有被执行函数作用域的变量,就形成了闭包。也就是说可以在内部函数访问到外部函数的作用域。创建闭包的常见方式是,在一个函数内部创建另一个函数。
使用闭包,一可以读取函数中的变量,二可以将函数中的变量存储在内存中,保护变量不受污染。
典型的例子
function createAction(){
var message = "封闭环境内的变量";
}
-
闭包的原理 函数的执行分为两个阶段(预编译阶段和执行阶段),在编译阶段,如果发现内部函数使用了外部函数的变量,则会在内存中创建一个“闭包”对象并保存对应的变量值,如果已存在"闭包",则需要增加对应属性值即可。执行完成之后,函数执行上下文会被销毁,函数对闭包对象的引用也会被销毁,但是其内部的函数还在用该“闭包”的引用,所以内部函数可以继续使用“外部函数”中的变量。
-
闭包的优点 1.可以从内部函数访问外部函数的作用域中的变量,切访问的变量长期扎在内存中,可供之后使用 2.避免变量污染全局 3.把变量存在独立的作用域,作为私有成员存在。
-
闭包的缺点 1.因为内部函数保存了对外部变量的引用,所以使用不当会导致内存泄漏。 2.对处理速度影响。闭包的层级决定了引用的外部变量在查找时经过的作用域长度。
-
闭包的使用场景
- 模块封装,在各模块规范出现之前,都是用这种方式防止变量污染全局。
var Person = (function(){
var age = 10;
function Person(){}
Person.prototype.bar = function bar(){
return age;
}
return Person;
}())
2.在循环中创建闭包,防止取得意外的值
for(var i=0;i<3;i++){
document.getElementById('id'+i).onfocus = function(){
alert(i);
}
}
// 闭包解决
function makeCallback(num){
return function(){
alert(num);
}
}
for(var i=0;i<3;i++){
document.getElementById('id'+i).onfocus = makeCallback(i);
}
2、对作用域和作用域链的理解?
一、作用域
在运行代码中的某些特定部分中变量、函数、对象的可访问性。也就是说,作用域决定了代码区块中变量和其他资源的可见性。
function testFun(){
var testVariable = '变量测试';
}
testFun(); // 要先执行这个函数
console.log(testVariable) // Uncaught ReferenceError: testVariable is not defined
作用域就是一个独立的地盘,让变量不会暴露出去。js中分三种作用域,全局作用域、函数作用域、块级作用域。
- 全局作用域
1.任何不在函数中或者大括号中声明的变量,都在全局作用域下,全局作用域下声明的变量可以在程序的任意位置访问。
// 全局变量
var greeting = 'Hello World!';
function greet() { console.log(greeting); }
// 打印 'Hello World!'
greet();
- 函数作用域 2.函数作用域也叫局部作用域,如果一个变量是在函数内部声明的它就在一个函数作用域下面。这些变量只能在函数内部访问,不能在函数以外去访问
function greet() {
var greeting = 'Hello World!';
console.log(greeting);
}
greet(); // 打印 'Hello World!'
console.log(greeting); // 报错: Uncaught ReferenceError: greeting is not defined
- 块级作用域
ES6引入了
let和const关键字,和var关键字不同,在大括号中使用let和const声明的变量存在于块级作用域中
{
// 块级作用域中的变量
let greeting = 'Hello World!';
var lang = 'English';
console.log(greeting); // Prints 'Hello World!'
}
console.log(lang); // 变量 'English'
console.log(greeting);// 报错:Uncaught ReferenceError: greeting is not defined
二、作用域链
-
自由变量 当前作用域没有定义的变量,这成为自由变量。自由变量的值是向父级作用域寻找,确切的说道创建这个函数的那个作用域去取值。
-
作用域链 如果父级没有,再一层层向上寻找,直到找到全局作用域还没找到,就放弃,这种一层一层的关系,就是作用域链。
3、对执行上下文的理解?
简单来说,执行上下文就是评估和执行js代码的环境的抽象概念。每当js代码在运行的时候,它都是在执行上下文中运行。
- 类型
-
全局执行上下文 -- 默认上下文,任何不在函数内部的代码都在全局上下文中。它会执行两件事情:创建一个全局的window对象,设置this的值等于这个全局对象。一个程序 中只会有一个全局执行上下文。
-
函数执行上下文 -- 每次调用函数时,都会为该函数创建一个新的执行上下文。每个函数都有自己的执行上下文,但是只有在函数被调用时才创建。一个程序中可以存在任意数量的执行上下文。
-
执行eval函数内部的代码也会有它属于自己的执行上下文,但是不经常用到eval。
- 执行上下文栈
- js用执行上下文栈来管理执行上下文
- 当js执行代码时,首先遇到全局代码,会创建一个全局执行上下文并且压入执行栈中,每当遇到一个函数调用,就会为该函数创建一个新的执行上下文并压入栈顶,引擎会执行上下文栈顶的函数,当函数执行完成之后,执行上下文从栈中弹出,继续执行下一个上下文。当所有的代码都执行完毕之后,从栈中弹出全局执行上下文。
let a = 'Hello World';
function first(){
console.log('first function');
second();
console.log('Again first function');
}
function second(){
console.log('second function');
}
first(); // 执行顺序 先执行second(),再执行first()
- 执行上下文的生命周期
执行上下文的生命周期包括创建阶段和执行阶段。
在js代码执行之前,执行上下文将经历创建阶段,在创建阶段会发生三件事情:
1.this值的绑定。
在全局执行上下文中,this的值指向全局对象。在函数执行上下文中,this的值取决于该函数是如何被调用的。如果它被一个引用对象调用,那么this会被设置成那个对象,否则this的值被设置为全局对象或者undefined。
2.创建词法环境组件。
3.创建变量环境组件。
简单来说执行上下文就是指:
在执行一点JS代码之前,需要先解析代码。解析的时候会先创建一个全局执行上下文环境,先把代码中即将执行的变量、函数声明都拿出来,变量先赋值为undefined,函数先声明好可使用。这一步执行完了,才开始正式的执行程序。
在一个函数执行之前,也会创建一个函数执行上下文环境,跟全局执行上下文类似,不过函数执行上下文会多出this、arguments和函数的参数。
- 全局上下文:变量定义,函数声明
- 函数上下文:变量定义,函数声明,
this,arguments
this、call、bind、apply
1、对this对象的理解?
this是执行上下文中的一个属性,它指向最后一次调用这个方法的对象。在实际的开发中,this的指向通过以下几种调用模式来判断。
- 函数调用模式 在全局作用域下,直接调用函数,this指向window
function fn(){ console.log(this); // this指向的是window对象 } fn(); - 方法调用模式 函数作为一个对象的方法来调用时,this指向这个对象。
var o = {
user:"人生代码",
fn:function(){
console.log(this.user); // 人生代码
}
}
o.fn();
- 构造器调用模式 如果一个函数用new调用时,函数执行前会新创建一个对象, this指向这个新创建的对象
function Fn(){
this.user = "人生代码";
}
var a = new Fn();
console.log(a.user); // 人生代码
- app、call、bind模式
三个方法都是显示的指定调用函数的this指向。其中apply方法接受两个参数:一个是this绑定的对象,一个是参数数组。call方法接收的参数,第一个是this绑定的对象,后面的参数是传入函数执行的参数。bind方法通过传入一个参数,返回一个this绑定了传入对象的新函数。这个函数的this指向除了使用new时会被改变,其他情况都不会被改变。
var FnOne=function(){
this.name="张三";
}
var FnTwo=function(){
console.log(this.name);//输出 张三
}
var fnOne=new FnOne();
FnTwo.call(fnOne);//改变fnTwo方法的this指向fnOne对象
使用构造器调用模式的优先级最高,然后是 apply、call 和 bind 调用模式,然后是方法调用模式,然后是函数调用模式
2、call()、apply()、bind()区别?
call、apply、bind都是改变this指向的方法。
- apply 接受两个参数,第一个参数指定了函数体内 this 对象的指向,第二个参数为一个带下标的集合,这个集合可以为数组,也可以为类数组,apply 方法把这个集合中的元素作为参数传递给被调用的函数。
fn.apply(obj, [1, 2])
- call 传入的参数数量不固定,跟 apply 相同的是,第一个参数也是代表函数体内的 this 指向,从第二个参数开始往后,每个参数被依次传入函数
fn.call(obj, 1, 2);
- 语法和call一模一样,区别在于立即执行还是等待执行,bind不兼容IE6~8
fn.bind(obj, 1, 2); // 改变fn中的this,fn并不执行
3、实现call、apply、bind函数?
- call
Function.prototype.myCall = function(context) {
// 判断调用对象
if(typeof this !== 'function'){
console.log('type error')
}
// 获取参数
let args = [...arguments].slice(1);
let result = null;
// 判断contetx是否传入
context = context || window;
// 将调用函数设为对象的方法
context.fn = this;
// 调用函数
result = context.fn(...args);
// 将属性删除
delete context.fn;
return result;
}
- apply
Function.prototype.myApply = function(context) {
// 判断调用对象
if(typeof this !== 'function'){
console.log('type error')
}
let result = null;
// 判断contetx是否传入
context = context || window;
// 将调用函数设为对象的方法
context.fn = this;
// 调用函数
if(arguments[1]){
result = context.fn(...arguments[1]);
} else {
result = context.fn();
}
// 将属性删除
delete context.fn;
return result;
}
- bind
Function.prototype.myBind = function(context) {
// 判断调用对象
if(typeof this !== 'function'){
console.log('type error')
}
// 获取参数
let args = [...arguments].slice(1);
let fn = this;
return function Fn(){
return fn.apply(
this instanceof Fn ? this : context, args.concat(...arguments)
)
}}
异步编程
1、 异步编程实现的方式?
- 回调函数 使用回调函数的方式有一个缺点,多个回调函数嵌套的时候会造成回调函数地狱,上下两层的回调函数的代码耦合度太高,不利于代码的可维护性。
// 嵌套地狱
请求1(function(请求结果1){
请求2(function(请求结果2){
请求3(function(请求结果3){
请求4(function(请求结果4){
请求5(function(请求结果5){
请求6(function(请求结果3){
...
})
})
})
})
})
})
- Promise方式,使用Promise的方式可以将嵌套的回调函数作为链式调用,但是使用这种方法,有时会造成多个then的链式调用,可能会造成代码的语义不够明确。
new Promise(请求1)
.then(请求2(请求结果1))
.then(请求3(请求结果2))
.then(请求4(请求结果3))
.then(请求5(请求结果4))
.catch(处理异常(异常信息))
- generator的方式 通过yield关键字,把函数的执行流挂起,通过next()方法可以切换到下一个状态,为改变执行流程提供了可能,从而为异步编程提供解决方法。generator最大的特点就是可以控制函数的执行。
function* myGenerator() {
yield '1'
yield '2'
return '3'
}
const gen = myGenerator(); // 获取迭代器
gen.next() //{value: "1", done: false}
gen.next() //{value: "2", done: false}
gen.next() //{value: "3", done: true}
- async/await 函数方式,async函数是generator和promise实现的一个自动执行的语法糖,它内部自带执行器,当函数内部执行到一个await语句的时候,如果语句返回一个promise对象,那么函数将会等待promise对象的状态变成resolve后再继续向下执行。因此可以将异步逻辑,转化为同步的顺序来书写,并且这个函数可以自动执行。
async function async1() {
return "1"
}
console.log(async1()) // -> Promise {<resolved>: "1"}
2、 对AJAX的理解,实现一个AJAX请求?
AJAX是Asynchrronous JavaScript and XML的缩写,通过Javascript的异步通信,从服务器获取XML文档从中提取数据,再更新当前网页的对应部分,而不是刷新整个网页。
// promise封装实现
const getJSON = (url) => {
let promise = new Promise((resolve,reject)=>{
let xhr = new XMLHttpRequest();
xhr.open('GET',url,true);
xhr.onreadystatechange = function(){
if(this.rreadyState !== 4) return;
// 当请求成功或失败时,改变Promise的状态
if(this.status === 200){
resolve(this.response);
} else {
reject(new Error(this.statusText))
}
};
// 设置错误监听函数
xhr.onerror = function() {
reject(new Error(this.statusText));
};
// 设置响应的数据类型
xhr.responseType = "json";
// 设置请求头信息
xhr.setRequestHeader("Accept", "application/json");
// 发送 http 请求
xhr.send(null);
});
return promise;
}
3、xhr、ajax、axios、fetch的区别
- XMLHttpRequest对象
现代浏览器,最开始和服务器交换数据,都是使用的XMLHttpRequest对象,可以使用JSON、XML、HTML和text文本等格式发送和接收数据。
if (window.XMLHttpRequest) { // model browser
xhr = new XMLHttpRequest()
} else if (window.ActiveXObject) { // IE 6 and older
xhr = new ActiveXObject('Microsoft.XMLHTTP')
}
xhr.open('POST', url, true)
xhr.send(data)
xhr.onreadystatechange = function () {
try {
// TODO 处理响应
if (xhr.readyState === XMLHttpRequest.DONE) {
// XMLHttpRequest.DONE 对应值是 4
// Everything is good, the response was received.
if (xhr.status === 200) {
// Perfect!
} else {
// There was a problem with the request.
// For example, the response may hava a 404 (Not Found)
// or 500 (Internal Server Error) response code.
}
} else {
// Not ready yet
}
} catch (e) {
// 通信错误的事件中(例如服务器宕机)
alert('Caught Exception: ' + e.description)
}
}
优点:
1.不重新加载页面的情况下更新网页
2.页面已加载后从服务器请求/接收数据。
缺点: 使用繁琐,对于早期的IE浏览器有自己的实现,需要写兼容性代码
- jQuery ajax 为了方便的操作DOM,并且规避一些浏览器兼容的问题,产生jQuery。里面的AJAX请求也兼容各浏览器,其实是对XMLHTTPRequest对象的封装。
$.ajax({
type: 'POST',
url: url,
data: data,
dataType: dataType,
success: function () {},
error: function () {}
})
优点:
1.对原生XML的封装,做兼容处理,简化使用
2.增加对JSONP的支持。
缺点: 1.多个请求,有依赖关系,会形成回调地狱。
2.本身是针对MVC的编程,不符合现在前端MVVM的浪潮。
- axios Axios是基于Promise的Http库,可以用在浏览器和node.js中,本质上还是对XML的封装,只不过是Promise的封装。
axios({
method: 'post',
url: '/user/12345',
data: {
firstName: 'liu',
lastName: 'weiqin'
}
})
.then(res => console.log(res))
.catch(err => console.log(err))
优点:
1.从浏览器中创建XMLHttpRequests
2. 从 node.js 创建 http 请求
3.支持 Promise API
4.拦截请求和响应
5.转换请求数据和响应数据
6.取消请求
7.自动转换 JSON 数据
8.客户端支持防御 XSRF
缺点: 1.只支持现代浏览器。
- fetch
Fetch API提供了一个JAvascript接口,用于访问和操作HTTP管道的部分。它还提供了一个全局的fetch()方法,该方法提供了一种简单、合理的方式在跨网络异步获取资源。
fetch是低层次的API,代替XHR,可以轻松处理各种格式,非文本化格式。
fetch('http://example.com/movies.json')
.then(function(response) {
return response.json();
})
.then(function(myJson) {
console.log(myJson);
});
优点:
- 语法简洁,更加语义化
- 基于标准 Promise 实现,支持 async/await
- 同构方便
- 更加底层,提供的API丰富(request, response)
- 脱离了XHR,是ES规范里新的实现方式
- 符合关注分离,没有将输入、输出和用事件来跟踪的状态混杂在一个对象里
缺点:
fetch只对网络请求报错,对400,500都当做成功的请求,需要封装去处理。(即使HTTP响应的状态码是404或500,从fetch()返回的Promise不会被标记为 reject。只有当网络故障时或请求被阻止时,才会标记为reject)fetch默认不会带cookie,需要添加配置项。(如果站点依赖于用户session,则会导致未经认证的请求(要发送cookies,必须设置credentials选项))fetch不支持abort,不支持超时控制,使用setTimeout及Promise.reject的实现超时控制并不能阻止请求过程继续在后台运行,造成了流量的浪费。fetch没有办法原生监测请求的进度,而XHR可以。
4、setTimeout、Promise、Async/Await的区别,宏任务、微任务?
5.对Promise的理解
6.Promise的基本用法
7.Promise解决了什么问题
8.Promise.all和Promise.race的区别?
持续更新~~