let、const 以及 var 的区别是什么?
- let 和 const 定义的变量不会出现变量提升,而 var 定义的变量会提升。
- let 和 const 是JS中的块级作用域(
{}
) - let 和 const 不允许重复声明(会抛出错误)
- let 和 const 定义的变量在定义语句之前,如果使用会抛出错误(形成了暂时性死区),而 var 不会。
- const 声明一个只读的常量。一旦声明,常量的值就不能改变(如果声明是一个对象,那么不能改变的是对象的引用地址)
Tip: 暂时性死区
在代码块内,使用 let/const 命令声明变量之前,该变量都是不可用的(会抛出错误)。这在语法上,称为“暂时性死区”。暂时性死区也意味着 typeof 不再是一个百分百安全的操作
typeof x; // ReferenceError(暂时性死区,抛错): x is not defined
let x;
typeof y; // 值是undefined,不会报错
暂时性死区的本质就是,只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。
(伪)类数组可以转换为数组:
类数组是一个普通对象,而真实的数组是Array类型。
常见的类数组有:
- 函数的参数 arguments,
- DOM 对象列表(比如通过 document.querySelectorAll 得到的列表),
- jQuery 对象 (比如 $(“div”))
//第一种方法
[...arrayLike];
//第二种方法
Array.from(arrayLike);
//第三种方法:
Array.prototype.slice.call(arrayLike, start);
new 关键字做了什么?
- 创建了一个新对象,并且this变量引用了该对象,新对象同时还继承了构造函数的原型
- 属性和方法被this加入到所引用的新对象中(重新绑定this,使构造函数的this指向新对象 )
- 新创建的对象由 this 所引用,最后隐式的返回this(return this),新创建的对象就拥有了构造函数的属性和方法了
原型链
当我们访问一个对象的属性时,如果这个对象内部不存在这个属性,它就会去它的原型(prototype
)上去找这个属性,这个原型上没有的话,(原型也是一个对象)就会去原型的原型上去找,以此类推。最后找到Object,如果Object.prototype上也没有的话,那就是null
原型链是实现继承的主要方法
用原生js(构造函数)写继承
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.sayHi = function() {
console.log(this)
}
function man(name, age, sex) {
Person.call(this, name, age)
this.sex = sex
}
man.prototype.sing = function() {
console.log(this.name, this.age, this.sex)
}
// man.prototype = new Person() 语义化不强,不适合
man.prototype = Object.create(Person.prototype)
// Object.create(protoObj) 会创建一个新对象,并且这个新对象的prototype会指向protoObj
man.prototype.constructor = man // 我们需要手动定义man.prototype的构造器,指回构造函数本身
var zs = new man('zs',18,'男')
// zs => man {name: "zs", age: 18, sex: "男"}
zs.__proto__ === man.prototype // true
man.prototype.__proto__ === Person.prototype // true
Person.prototype.__proto__ === Object.prototype // true
Object.prototype.__proto__ === null // true
Tip:
man.prototype = Object.create(Person.prototype)
man.prototype.constructor = man
// => 以上代码等价于以下
man.prototype = Object.create(Person.prototype, {
constructor: {
value: man,
enumerable: false,
writable: false,
configurable: false
}
});
面试题
Object.prototype.
__proto__
// null
Function.prototype.__proto__
// Object.prototype
Object.__proto__
// Function.prototype
以上涉及Function的原型问题:
ES6类的继承
Tip :
- ES6中类没有变量提升
- 通过构造函数创建实例,是可以变量提升的。 es6中的类,必须先有类,才可以实例化。
- constructor方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。一个类必须有constructor方法,如果没有显式定义,一个空的constructor方法会被默认添加
class Father {
constructor(name){
this.name = name
}
sing() {
return this.name + '我在唱歌'
}
}
// ES6中用 extends 关键字继承
class son extends Father {
constructor(name,score) {
super(name)
this.score = score
}
doing() {
return this.name + ',' + this.sing()
}
}
let ldh = new son('刘德华',99)
ldh
// => son {name: "刘德华", score: 99}
ldh.doing()
// => "刘德华,刘德华我在唱歌"
类和构造函数的区别
- 类必须使用new调用,否则会报错。这是它跟普通构造函数的一个主要区别,后者不用new也可以执行。
- 类的所有实例共享一个原型对象**。
- 类的内部,默认就是严格模式,所以不需要使用use strict指定运行模式。
构造函数特点:
- 构造函数有原型对象prototype。
- 构造函数原型对象prototype里面有constructor,指向构造函数本身。
- 构造函数可以通过原型对象添加方法。
- 构造函数创建的实例对象有
__proto__
原型,指向构造函数的原型对象。
类的特点:
- class本质还是function
- 类的所有方法都定义在类的prototype属性上
- 类创建的实例,里面也有
__proto__
指向类的prototype原型对象 - 新的class写法,只是让对象原型的写法更加清晰,更像面向对象编程的语法而已。
- ES6的类其实就是语法糖。
Tip:
什么是语法糖?加糖后的代码功能与加糖前保持一致,糖在不改变其所在位置的语法结构的前提下,实现了运行时的等价。
语法糖没有改变语言功能,但增加了程序员的可读性。
基本数据类型和复杂数据类型的区别:
1.内存分配不同
- 基本数据类型存储在栈中。
- 复杂数据类型存储在堆中,栈中存储的地址,是指向堆中的引用地址,属性及值存储在堆中。
2.访问机制不同
-
基本数据类型是按值访问
-
复杂数据类型是按引用访问,JS不允许直接访问保存在堆内存中的对象,在访问一个对象时,首先得到的是这个对象在栈内存中的地址,然后按照这个地址去获得存储在堆中的值。
-
a.基本数据类型作为参数时,修改基本数据类型的参数不影响原有数据,
b.复杂数据类型,是通过引用地址来访问的,所以作为参数时,修改属性后,会影响原有数据。
let b = { age: 10 }
let a = b;
a.age = 20;
console.log(a); // { age: 20 }
console.log(b); // { age: 20 }
基本数据类型:
//基本数据类型
let b = 10
function change(info) {
info=20;
}
// info=b;基本数据类型,拷贝的是值得副本,二者互不干扰
change(b);
console.log(b);//10
复杂数据类型:
//复杂数据类型
let b = {
age: 10
}
function change(info) {
info.age = 20;
}
//info=b;根据第三条差异,可以看出,拷贝的是地址的引用,修改互相影响。
change(b);
console.log(b);//{ age: 20 }
Tip:
- 存储的时候,所有的简单类型都存放在栈中,所有的复杂类型的内容多存在堆中。
- 复杂类型的值虽然存在堆中,但是他会有一个地址存在栈中。
- 可以由这个栈里面的地址找到对应的堆中的复杂类型的数据
Boolean
该抽象操作负责处理非布尔值到布尔值转换.
type | result |
---|---|
null | false |
undefined | false |
boolean | 不转换 |
string | “” => false; 其它 => true |
number | +0, −0, NaN => false; 其它 => true |
Object | true |
真值 & 假值
假值(强制类型转换false的值) => undefined
, null
, false
, +0
, -0
, NaN
, ""
.
真值(强制类型转换true的值) => 除了假值, 都是真值.
隐式强制类型转换
+/ -/ ! / ~
+/- 一元运算符
=> 运算符会将操作数进行ToNumber处理.!
=> 会将操作数进行ToBoolean处理.~
=> (~x)相当于 -(x + 1) eg: ~(-1) ==> 0; ~(0) ==> 1; 在if (…)中作类型转换时, 只有-1
时, 才为假值.+加号运算符
=> 若操作数有String类型, 则都进行ToString处理, 字符串拼接. 否则进行ToNumber处理, 数字加法.
条件判断
if (...)
,for(;;;)
,while(...)
,do...while(...)
中的条件判断表达式.? :
中的条件判断表达式.||
和&&
中的中的条件判断表达式.
以上遵循Boolean规则
||(逻辑‘或’) 和 &&(逻辑’与’)
- 返回值是两个操作数的中的一个(且仅一个). 首先对第一个操作数条件判断, 若为非布尔值则进行ToBoolean强制类型转换.再条件判断.
||
=> 条件判断为true, 则返回第一个操作数; 否则, 返回第二个操作数. 相当于 a ? a : b;&&
=> 条件判断为true, 则返回第二个操作数; 否则, 返回第一个操作数, 相当于 a ? b : a;
onscroll onblur onfocus onmouseenter onmouseleave 没有事件冒泡 不能进行事件委托
ECMAScript5标准下,严格模式中匿名函数的的this指向的是undefined,不是window
用了严格模式**“use strict”**,严格模式下无法再意外创建全局变量,所以this
不为window
而为undefined
<html>
<script type="text/javascript">
"use strict";
var foo = function foo(){
console.log(this)
};
foo(); //undefined
</script>
</html>
严格模式为什么对箭头函数没有效果,返回还是window
Given that this
comes from the surrounding lexical context, strict mode
rules with regard to this
are ignored.
lexical means that this refers to the this value of a lexically enclosing function.
综上所述,在箭头函数中,this
为lexical
类型,lexical
意味着这个this
指是所在封闭函数中this
,所以严格模式会自动忽视use strict
,所以this
如下所示:
<html>
<script type="text/javascript">
var foo = () => {
"use strict";
console.log(this)
};
foo(); //Window
</script>
</html>
箭头函数中,this
指向运行时所在的对象,而use strict
被移到函数内了,所以this
为全局变量window
。
函数中的 this指向问题
- 普通函数:window
- 对象方法:该方法所属对象
- 通过new关键字,构造函数&构造函数.prototype上的方法:实例
- 事件绑定:事件源
- 事件三要素:事件源,事件类型,事件处理函数
- 事件流:捕获、目标、冒泡
- 定时器:window
- 自执行函数:window
- 箭头函数: 箭头函数没有自己的this,箭头函数中的this继承于外层代码库中的this.
js执行机制
js执行的时候,一定是优先执行所有的同步代码,等待所有的同步代码执行完毕之后,再把异步代码放进来执行
让页面一加载,就会把所有的代码分开,同步代码都放到执行栈中执行,异步代码放到任务队列
执行栈(放同步代码地方)(所有的代码都放到执行栈中执行)
任务队列(放异步代码的地方)
数组的哪些API会改变原数组
(变异方法)修改原数组的API有:
push pop unshift shift splice reverse sort fill copyWithin
(非变异方法)不修改原数组的API有:
slice map forEach filter reduce entries find findIndex includes concat some every
**添加到 . e x t e n d ( ) 上 的 是 全 局 函 数 : ∗ ∗ .extend( ) 上的是全局函数:** .extend()上的是全局函数:∗∗.xxx 调用
添加到jQuery.fn.extend( )上的是原型对象上的函数,需要通过jQuery的对象来调用 $().xxx调用
什么是闭包?闭包的作用是什么?闭包有哪些使用场景?
闭包是指有权访问另一个函数作用域中的变量的函数,创建闭包最常用的方式就是在一个函数内部创建另一个函数。
闭包的作用有:
- 封装私有变量
- 模仿块级作用域(ES5中没有块级作用域)
- 实现JS的模块
内存泄漏
内存泄露是指一块被分配的内存既不能使用,又不能回收,直到浏览器进程结束
浏览器中也是采用自动垃圾回收方法管理内存,但由于浏览器垃圾回收方法有 bug,会产生内存泄露。
- 闭包会造成内存泄漏
- 全局变量引起的内存泄漏
- 监听事件添加后,没有移除
防抖和节流的区别是什么?
防抖和节流的作用都是防止函数多次调用。区别在于,假设一个用户一直触发这个函数,且每次触发函数的间隔小于设置的时间,防抖的情况下只会调用一次,而节流的情况会每隔一定时间调用一次函数。
防抖(debounce): n秒内函数只会执行一次,如果n秒内高频事件再次被触发,则重新计算时间
防抖的应用场景:
- 每次 resize/scroll 触发统计事件
- 文本输入的验证(连续输入文字后发送 AJAX 请求进行验证,验证一次就好)
节流(throttle): 高频事件在规定时间内只会执行一次,执行一次后,只有大于设定的执行周期后才会执行第二次。
函数节流的应用场景有:
- DOM 元素的拖拽功能实现(mousemove)
- 射击游戏的 mousedown/keydown 事件(单位时间只能发射一颗子弹)
- 计算鼠标移动的距离(mousemove)
- Canvas 模拟画板功能(mousemove)
- 搜索联想(keyup)
- 监听滚动事件判断是否到页面底部自动加载更多:给 scroll 加了 debounce 后,只有用户停止滚动后,才会判断是否到了页面底部;如果是 throttle 的话,只要页面滚动就会间隔一段时间判断一次
为什么 0.1 + 0.2 != 0.3 ?
0.1 + 0.2 != 0.3 是因为在进制转换和进阶运算的过程中出现精度损失 TIp
===
和==
的区别
===
:严格等于,会先比较两边的类型,只有类型相同,才会比较数据==
:普通等于,如果两边的类型不同,会先进行隐式转换,再比较数据===
比==
性能高一丢丢,占用的资源少一丢丢,因为不存在隐式转换,效率会高一些,所以推荐使用严格等于:===
实例方法与静态方法
- 实例方法,是通过new关键字创建出来的实例调用的方法
- 静态方法,不需要new实例,可以直接通过构造函数调用的方法
get 和 post的区别
- get用来获取数据,post用来提交数据
- get参数通过URL传递,post放在请求体中
- get在浏览器回退时是无害的,而post会提示你,再次提交表单请求
- get请求在URL中传送的参数是有长度限制的(一般限制在 2~8K 之间),而post的数据则可以非常大(PHP 默认是 2M)
- post比get安全,因为get参数直接暴露在URL上,所以不能用来传递敏感信息
- get请求只能进行url编码,而post支持多种编码方式
- get请求会保存在浏览器历史记录中,还可能保存在web服务器的日志中
一个页面从输入 URL 到页面加载显示完成,这个过程中都发生了什么?
1、浏览器地址栏输入url
2、浏览器会先查看浏览器缓存–系统缓存–路由缓存,如有存在缓存,就直接显示。如果没有,接着第三步
3、域名解析(DNS)获取相应的ip
4、浏览器向服务器发起tcp连接,与浏览器建立tcp三次握手
5、握手成功,浏览器向服务器发送http请求,请求数据包
6、服务器请求数据,将数据返回到浏览器
7、浏览器接收响应,读取页面内容,解析html源码,生成DOm树
8、解析css样式、浏览器渲染,js交互绑定多个域名,数量不限;
iframe
a>通过iframe实现跨域;
b>使用iframe解决IE6下select遮挡不住的问题
c>通过iframe解决Ajax的前进后退问题
d>通过iframe实现异步上传。(Easyui中form组件就是用的iframe,实现表单提交时,可以提交上传域)
小程序生命周期
es6语法
- let const
- 箭头函数
- 解构赋值
- 反引号
xxx+${变量}
- promise
- 剩余参数 和扩展运算符
- async await
组件化开发优点
- 提高开发效率
- 方便重复使用
- 简化调试步骤
- 提升整个项目的可维护性
- 便于协同开发