基础
常见的数组方法有那些,那些会改变原数组
改变原数组的方法
- push : 在数组的末尾添加
- pop : 在数组的末尾删除
- unshift : 在数组的头部添加
- shift:在数组的头部删除
- splice: 在指定位置删除/添加元素
- fill :填充数组
- forEach : 循环每一项,类似for循环
- reverse :反转数组
- sort : 排序
不会改变数组的方法
- slice : 截取指定位置的元素
- concat: 连接两个数组
- filter: 根据条件过滤数组
- find : 根据添加返回符合添加的第一个元素
- map:通过指定函数处理数组的每个元素,并返回处理后的数组。
js的几种数据类型
- 基本数据类型
string number boolean undefined null
- 引用性数据类型
object array
判断类型的方法
- typeof xx : 只能判断基础类型和引用类型
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 注意
2.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.Object.prototype.toString.call() 使用 Object 对象的原型方法 toString 来判断数据类型:
var a = Object.prototype.toString;
console.log(a.call(2));
console.log(a.call(true));
console.log(a.call('str'));
console.log(a.call([]));
console.log(a.call(function(){}));
console.log(a.call({}));
console.log(a.call(undefined));
console.log(a.call(null));
原理:
同样是检测对象obj调用toString方法,obj.toString()的结果和Object.prototype.toString.call(obj)的结果不一样,这是为什么?
这是因为toString是Object的原型方法,而Array、function等类型作为Object的实例,都重写了toString方法。不同的对象类型调用toString方法时,根据原型链的知识,调用的是对应的重写之后的toString方法(function类型返回内容为函数体的字符串,Array类型返回元素组成的字符串…),而不会去调用Object上原型toString方法(返回对象的具体类型),所以采用obj.toString()不能得到其对象类型,只能将obj转换为字符串类型;因此,在想要得到对象的具体类型时,应该调用Object原型上的toString方法。
if(x)的x 什么时候会是真
false :
‘’ , false , 0 , -0 , null ,undefined , NaN
true :
’ '(里面有空格), ‘str’ , 123 , [] , {} ,
变量提升和函数提示的优先级
函数 > 变量
new Doo() > new Doo > Doo()
null和undefined区别
首先 Undefined 和 Null 都是基本数据类型,这两个基本数据类型分别都只有一个值,就是 undefined 和 null。
undefined 代表的含义是未定义,null 代表的含义是空对象。一般变量声明了但还没有定义的时候会返回 undefined,null主要用于赋值给一些可能会返回对象的变量,作为初始化。
undefined 在 JavaScript 中不是一个保留字,这意味着可以使用 undefined 来作为一个变量名,但是这样的做法是非常危险的,它会影响对 undefined 值的判断。我们可以通过一些方法获得安全的 undefined 值,比如说 void 0。
当对这两种类型使用 typeof 进行判断时,Null 类型化会返回 “object”,这是一个历史遗留的问题。当使用双等号对两种类型的值进行比较时会返回 true,使用三个等号时会返回 false。
为什么0.1+0.2 ! == 0.3,如何让其相等
双精度问题
(n1 + n2).toFixed(2) // 注意,toFixed为四舍五入
var let const 三者的区别
- 变量提升(var会提升到该作用域的最顶部)
let const 没有变量的提升,var 有
例如
var 定义的变量可以在使用之后定义
a = 10;
var = a ;
编译成的代码是
var a =undefined
a = 10
- 暂时性死区
c= 10 //这块区域就是暂时性死区
let c
console.log(c); // ReferenceError:
- 不能重复声明
var 可以重复声明 let const 不行会报错
- 块级作用域有效
{
var bb = 10
}
console.log(bb); //10
{
let bb = 10
}
console.log(bb); //ReferenceError: bb is not defined
this
- 普通函数的this指向Windows
function a() {
console.log('普通函数的this'+this);
}
a();//普通函数的this[object Window]
- 箭头函数的this 默认指向定义它时,所处上下文的对象的this指向。即ES6箭头函数里this的指向就是上下文里对象this指向,偶尔没有上下文对象,this就指向window
let o = {
name:'张三',
say:()=>{
console.log('箭头的this',this.name); //win 这里上下文没有函数对象就默认为window
}
}
o.say();
- 对象里面的this,谁调用就指向谁
let o = {
name:'张三',
say:function(){
console.log(this.name); // 张三
}
}
o.say();
箭头函数与普通函数的区别
- 箭头函数比普通函数更加简洁
- 箭头函数没有自己的this
箭头函数的this指向的是所在的作用域。所以箭头函数中this的指向在它在定义时已经确定了,之后不会改变。 - 箭头函数继承来的this指向永远不会改变
var id = 'GLOBAL';
var obj = {
id: 'OBJ',
a: function(){
console.log(this.id);
},
b: () => {
console.log(this.id);
}
};
obj.a(); // 'OBJ'
obj.b(); // 'GLOBAL'
new obj.a() // undefined
new obj.b() // Uncaught TypeError: obj.b is not a constructor
对象obj的方法b是使用箭头函数定义的,**这个函数中的this就永远指向它定义时所处的全局执行环境中的
this,**即便这个函数是作为对象obj的方法调用,this依旧指向Window对象。
需要注意,定义对象的大括号{}是无法形成一个单独的执行环境的,它依旧是处于全局执行环境中。
- call()、apply()、bind()等方法不能改变箭头函数中this的指向
- 箭头函数不能作为构造函数使用
- 箭头函数没有自己的arguments
- 箭头函数没有prototype
事件流中的事件冒泡和事件捕获
事件流描述了页面接收事件的顺序。
历史:当时两大公司IE 和 Netscape 开发团队提出了几乎完全相反的事件流方案。IE 将支持事件冒泡流,而 Netscape Communicator 将支持事件捕获流。
事件冒泡:以绑定事件的元素为起点,然后向上传播至没有那么具体的元素
<!DOCTYPE html>
<html>
<head>
<title>Event Bubbling Example</title>
</head>
<body>
<div id="myDiv">Click Me</div>
</body>
</html>
在点击页面中的<div>元素后,click 事件会以如下顺序发生:
(1) <div>
(2) <body>
(3) <html>
(4) document
事件捕获的意思是最不具体的节点应该最先收到事件,而最具体的节点应该最后收到事件。
点击
(1) document
(2) <html>
(3) <body>
(4) <div>
DOM2 Events 规范规定事件流分为 3 个阶段:事件捕获、到达目标和事件冒泡。
for in 与 for of 的区别
-
for in 遍历的是key 是 键。for of 遍历的是key是值
-
for in 遍历的是有可枚举属性的对象/数组,原型链上的对象也会去循环。如果不想遍历原型方法和属性的话,可以在循环内部判断一下,使用hasOwnProperty()方法可以判断某属性是不是该对象的实例属性
for of 遍历的是可迭代的对象/数组
-
for in 不可以遍历set map 。for of 可以
作用域和作用域链
- 全局作用域(可以理解为window)
- 函数作用域
函数作用域声明在函数内部的变零,一般只有固定的代码片段可以访问到 - 块级作用域
使用ES6中新增的let和const指令可以声明块级作用域,块级作用域可以在函数中创建也可以在一个代码块中的创建(由{ }包裹的代码片段) - 作用域链
作用域链: 在当前作用域中查找所需变量,但是该作用域没有这个变量,那这个变量就是自由变量。如果在自己作用域找不到该变量就去父级作用域查找,依次向上级作用域查找,直到访问到window对象就被终止,这一层层的关系就是作用域链。
作用域链的作用是保证对执行环境有权访问的所有变量和函数的有序访问,通过作用域链,可以访问到外层环境的变量和函数
高级
object.assign和扩展运算法是深拷贝还是浅拷贝,两者区别
let outObj = {
inObj: {a: 1, b: 2}
}
let newObj = {...outObj}
newObj.inObj.a = 2
console.log(outObj) // {inObj: {a: 2, b: 2}}
let outObj = {
inObj: {a: 1, b: 2}
}
let newObj = Object.assign({}, outObj)
newObj.inObj.a = 2
console.log(outObj) // {inObj: {a: 2, b: 2}}
可以看到,两者都是浅拷贝。
Object.assign()方法接收的第一个参数作为目标对象,后面的所有参数作为源对象。然后把所有的源对象合并到目标对象中。它会修改了一个对象,因此会触发 ES6 setter。
扩展操作符(…)使用它时,数组或对象中的每一个值都会被拷贝到一个新的数组或对象中。它不复制继承的属性或类的属性,但是它会复制ES6的 symbols 属性
原型与原型链(有继承作用)
我们创建一个对象的时候都会有一个prototype属性,这个属性指向的就是一个对象也叫原型对象。当我们实例化对象的时候,实例的__proto__属性也指向该对象原型。这样就有一条链子出来,如果对象还有父亲。祖父就会形成一条原型链。如图所示
构造函数和普通函数
- 调用方式不同
普通函数调用方式:直接调用person();
构造函数调用方式:需要使用new关键字来调用 new person(); - this不同
普通函数中的this,在严格模式下指向undefined,非严格模式下指向window对象。
构造函数的this则是指向它创建的对象实例。 - 命名规范不同
闭包
闭包是指有权访问另一个函数作用域中变量的函数,创建闭包的最常见的方式就是在一个函数内创建另一个函数,创建的函数可以访问到当前函数的局部变量。
function a {
let ka = '1';
function b {
console.log(ka)
}
b()
}
两个用途:
闭包的第一个用途是使我们在函数外部能够访问到函数内部的变量。通过使用闭包,可以通过在外部调用闭包函数,从而在外部访问到函数内部的变量,可以使用这种方法来创建私有变量。
闭包的另一个用途是使已经运行结束的函数上下文中的变量对象继续留在内存中,因为闭包函数保留了这个变量对象的引用,所以这个变量对象不会被回收。
new关键字执行了什么
(1)首先创建了一个新的空对象
(2)设置原型,将对象的原型设置为函数的 prototype 对象。
(3)让函数的 this 指向这个对象,执行构造函数的代码(为这个新对象添加属性)
(4)判断函数的返回值类型,如果是值类型,返回创建的对象。如果是引用类型,就返回这个引用类型的对象。
function objectFactory() {
let newObject = null;
let constructor = Array.prototype.shift.call(arguments);
let result = null;
// 判断参数是否是一个函数
if (typeof constructor !== "function") {
console.error("type error");
return;
}
// 新建一个空对象,对象的原型为构造函数的 prototype 对象
newObject = Object.create(constructor.prototype);
// 将 this 指向新建对象,并执行函数
result = constructor.apply(newObject, arguments);
// 判断返回对象
let flag = result && (typeof result === "object" || typeof result === "function");
// 判断返回结果
return flag ? result : newObject;
}
// 使用方法
objectFactory(构造函数, 初始化参数);
js执行机制
- 概念
js代码分为同步代码和异步代码
同步任务会进入主线程,异步任务会进入Event Table(事件表),当事件表中的异步任务完成后会在Event Queue(事件队列)中注册回调函数。主线程任务全部完成后,才会完成Event Queue中的任务。js解析器会不断地重复检查主线程执行栈是否为空,从而形成了一个Event Loop(事件循环)
2. 任务细分
任务又可以进一步分为宏任务和微任务,这对js代码的执行有更为细致的影响,异步任务中的宏任务和微任务会进入不同的Event Queue事件队列,即Event Queue又可以分为宏任务队列和微任务队列。
setInterval会按照设定的时间间隔重复地在Event Queue注册回调函数,如果某一段时间主线程代码执行太久,那么setInterval的回调函数可能阻塞到一起执行,无法保持设定的时间间隔,如果此时setInterval用于动画,则体现为卡顿。