JS基础及高级知识
JS基础及高级知识
JS基础
判断
- ===: 一般单独只用作null 与 undefined 两种类型值判断, 例:
- typeof : 返回的数据类型为小写字符串,例:typeof ‘String’ === ‘string ‘
- instanceof
- typeof 能够检测出了null之外的原型类型(String、Number、Boolean、Undefined),对于对象类型能判断出function、其他的都为Object
- 判断一个值是否为数组,使用Array.isArray()‘
函数提升与变量提升
1
2
3
4
5
6
7
8fn1()//可执行
fn2()//此时不能被调用,因为变量声明提升, fn2在加载完js脚本后,会先将其赋值为undefined(先声明到window对象中,但是未赋值)
function fn1(){
//...
}
var fn2 = function(){
}
this指针
- 普通this指针
- 箭头函数中的this指
- instanceof :**
instanceof运算符用来检测constructor.prototype是否存在于参数object的原型链上** - 总结:
1. 直接调用函数,函数中的this永远指向window
2. 通过方法的形式调用函数,函数中的this指向调用该方法的对象
3. 通过构造函数的形式调用函数,函数的this指向新创建的那个对象
深浅拷贝
- 浅拷贝:重新在堆中创建内存,拷贝前后对象的基础数据类型互不受影响,单拷呗前后对象的应用类型因共享一块内存(堆空间),就是把父对像的属性,全部拷贝给子对象。
- 深拷贝:从堆内存中开辟一个新的区域 存放新的对象,将对象中的子对象进行递归拷贝,由于堆空间不同,所以两个对象互不影响。
1 | function deepClone(obj){ |
js回调函数
同步sync、异步async
1
2
3
4
5
6
7
8
9
10
11
12funciont a(){
console.log("execute a")
setTimeout(function(){
console.log("delayed execute a ")
},1000)
}
function b(){
console.lgo("execute b")
}
a()
b()- a 与 a 的延时就是异步 ,js引擎的event 队列空闲时才会去执行队列里等待的setTimeout的回调函数,即使事件设置为0 也会先执行b
调用 setTimeout 函数会在一个时间段过去后在队列中添加一个消息。这个时间段作为函数的第二个参数被传入。如果队列中没有其它消息,消息会被马上处理。但是,如果有其它消息,setTimeout 消息必须等待其它消息处理完。因此第二个参数仅仅表示最少的时间 而非确切的时间
回调函数定义:==A callback is a function that is passed as an argument to another function and is executed after its parent function has completed.==
- 我定义的函数
- 但是我没有主动调用这个函数
- 最终该函数执行了
回调函数分类
- dom时间回调函数
- 定时器回调函数
- ajax请求回调函数
- 生命周期回调函数
回调与同步、异步并没有直接的联系,回调只是一种实现方式,既可以有同步回调,也可以有异步回调,还可以有事件处理回调和延迟函数回调,这些在我们工作中有很多的使用场景
防抖函数
定义:当持续触发事件,一定时间内没有再触发事件 事件函数才会执行一次 如果设定的时间到来之前 有一次出发了时间 就更新开始延时。
在事件被触发n秒后再执行回调函数,如果在这n秒内又被触发,则重新计时。
1
2
3
4
5
6
7
8
9
10//未优化的版本,引入t,造成全局变量污染
var t = null;
inputEle.addEventListener("keyup", function(e){
clearTimeout(t); //每次触发,都把前面的定时器关闭,尽管第一次定时器并不存在
t = setTimeout(function(){ //开启新的定时器
//ajax(...); 发送请求到服务器
}, 300);
})1
2
3
4
5
6
7
8
9
10
11
12
13
14
15var input = document.getElementById('input')
function debounce(delay){
let timer
return function(value){
clearTimeout(timer)
timer = setTimeout(function(){
console.log(value)
},delay)
}
}
var debounceFunc = debounce(1000)
input.addEventListener('keyup',function(e){
debounceFunc(e.target.value)
})
//引入了自运行函数,当然是为了解决全局变量污染应用场景:
- 用户在输入框中连续输入一串字符后,只会在输入完后去执行最后一次的查询ajax请求,这样可以有效减少请求次数,节约请求资源;
- window的resize、scroll事件,不断地调整浏览器的窗口大小、或者滚动时会触发对应事件,防抖让其只触发一次;
节流函数
- 定义:规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效
1 | function thro(func,wait){ |
应用场景
- 鼠标连续不断地触发某事件(如点击),只在单位时间内只触发一次
- 页面的无限加载场景下,需要用户在滚动页面时,每隔一段时间发一次 ajax 请求,而不是在用户停下滚动页面操作时才去请求数据
- 监听滚动事件,比如是否滑到底部自动加载更多,用throttle来判断
总结防抖和节流的区别:
- 效果:函数防抖是某一段时间内只执行一次;而函数节流是间隔时间执行,不管事件触发有多频繁,都会保证在规定时间内一定会执行一次真正的事件处理函数。
- 原理:防抖是维护一个计时器,规定在delay时间后触发函数,但是在delay时间内再次触发的话,都会清除当前的 timer 然后重新设置超时调用,即重新计时。这样一来,只有最后一次操作能被触发。
节流是通过判断是否到达一定时间来触发函数,若没到规定时间则使用计时器延后,而下一次事件则会重新设定计时器。
js的作用域
全局作用域
- 页面打开时创建,关闭时销毁
- 编写在script标签中的变量和函数,作用域为全局,在页面的任意位置可以访问到
- 全局作用域中的全局对象为window,作为一个浏览器窗口,由浏览器调用,可以直接调用
- 全局作用域中声明的变量和函数会作为window对象的属性和方法进行保存
函数作用域
- 调用函数时创建,执行完毕销毁
- 每调用一次就会创建新的作用域,相互之间独立
- 在函数作用域访问变量,函数时,会先在自身作用域寻找,若没有找到,则会在函数的上一级寻找,直到找到全局作用域
深层理解
- 执行期的上下文
- ==在函数代码执行的前期,会创建一个执行期上下文的内部对象AO(作用域)==
- ==在全局代码执行的前期也会创建一个执行期的上下文对象GO==
- 内部的对象是预编译的时候创建出来的,因为函数在被调用时,会先进行预编译
- 执行期的上下文
函数作用域预编译
创建AO对象AO{}
找形参和变量声明,将变量和参数名当作AO对象的属性名,值为undefined
==实参形参相统一==
==在函数体里面找函数声明,值赋予函数体==
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25fucntion fn(a,c){
console.log(a) //function a(){}
var a = 123
console.log(a) //123
console.log(c) //function c(){}
function a(){}
if(false){
var d = 678
}
console.log(d) //undefined
coonsole.log(b) //undefined
var b = function(){}
console.log(b) //function (){}
function c(){}
console.log(c) //function c(){}
}
fn(1,2)
//分析过程
AO{
a:undefined --> 1 --> function a(){}
c:undefined --> 2 --> function c(){}
d: undefined
b: undefined
}
作用域链
含义:会被保存到一个隐式的属性中去[[scope]]这个属性时我们用户访问不到的,但是 的的确确是存在的,让js引擎来访问的,里面存储的就是作用域链(AO与GO的结合)
代码分析
1
2
3
4
5
6
7
8
9
10
11
12
13function a(){
var aa = 123
function b(){
var bb = 234
console.log(aa)
}
return b
}
var res = a()
res()
//分析:在a执行完后,b的定义与a所能看到的作用域是一样的,
//所以b函数能看到a在AO作用域中的存在
立即执行函数
含义:声明一个匿名函数,马上调用这个匿名函数
写法:
1
2
3
4
5
6
7
8(function(){alert('我是匿名函数')} ()) // 用括号把整个表达式包起来
(function(){alert('我是匿名函数')}) () //用括号把函数包起来
!function(){alert('我是匿名函数')}() // 求反,我们不在意值是多少,只想通过语法检查。
+function(){alert('我是匿名函数')}()
-function(){alert('我是匿名函数')}()
~function(){alert('我是匿名函数')}()
void function(){alert('我是匿名函数')}()
new function(){alert('我是匿名函数')}()作用::创建一个独立的作用域,这个作用域里面的变量,外面访问不到(即避免「变量污染」)。
==误区:不一定是声明后就调用,而是当其在script下的一层被声明时,会加载完就调用==
闭包
定义:「函数」和「函数内部能访问到的变量」(也叫环境)的总和,就是一个闭包。
特性
- 函数嵌套函数
- 内部函数可以引用内部函数的外部的参数和变量
- 参数和变量不会被垃圾回收机制回收
例子解释
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18//外部函数
function outFn(){
//外部函数的内部,此时闭包就已经产生了
var a = 2
//内部函数,内部函数的外部
function inFn(){
//内部函数内部
a++
console.log(a)
}
return inFn
}
var fn = outFn()
fn() //3
fn() //4
fn = null //闭包的生命周期结束,堆内存里的函数内容没有任何栈空间的变量去引用
//在inFn这里产生了closure,可以通过浏览器打断点观察作用
- 使用外部函数 内部的变量,在函数执行后任然存活在内存中(延长了局部变量的生命周期)
- 让外部函数的外部能够够操作到外部函数内的数据
经典应用
- 防抖
- 节流
Js-event-loop
- 执行栈与事件队列
- 执行栈 和 存放基础变量以及对象的引用不是一个栈空间,而是调用函数的栈空间,其会将待执行的方法压栈,压栈完毕后进行出栈执行(压栈时会带入执行上下文)
- 事件队列(Task Quene),当遇到异步代码时,会将其挂起,栈空间的方法代码继续出栈执行,并将异步代码加入到事件队列中,当主线程执行完所有同步代码后,会处理事件队列的代码。
- 事件队列时间上有两个,执行栈执行完毕时会立刻先处理所有微任务队列中的事件,然后再去宏任务队列中取出一个事件。同一次事件循环中,微任务永远在宏任务之前执行。
- 微任务(micro task)
new Promise()new MutaionObserver()
- 宏任务(macro task)
setInervalsetTimeout
- 微任务(micro task)
- 所谓事件队列(event queue)、消息队列(message queue)与任务队列(task queue) 都是指的一个callbak queue。
js prototype
出现prototype的前情分析
构造函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15//1.初始写法
function Person(name,height){
var alia = "noPerson";
this.name=name;
this.height=height;
this.hobby=function(){
return 'watching movies';
}
}
var boy=new Person('keith',180);
var girl=new Person('rascal',153);
console.log(boy.alia) // undefined
console.log(boy.name); //'keith'
console.log(girl.name); //'rascal'
console.log(boy.hobby===girl.hobby); //false上述分析:
- 其中alias属性访问不到,可以理解为私有属性,同理,这样定义的函数也可以理解为私有函数(作用域)
- 那么明显可以看到构造函数的缺点==命名hobby这个方法,在不同对象中都是同样的功能,但是开辟了两处堆空间去存==
prototype属性
- 作用:==为了解决 对象实例间无法共享属性的缺点(不仅仅是方法)==
- 含义:js中每个数据类型都是对象(除了null和undefined),而每个对象都继承自另外一个对象,后者称为“原型”(prototype)对象,只有null除外,它没有自己的原型对象,原型对象上的所有属性和方法,都会被对象实例所共享。
- 对于构造函数
Person()来说,prototype是作为构造函数的属性;对于对象实例boy,girl来说,prototype是对象实例的原型对象。所以prototype即是属性,又是对象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16function Person(name,height){
this.name=name;
this.height=height;
}
//2.加入prototype
Person.prototype.hobby=function(){
return 'watching movies';
}
console.log(boy.hobby===girl.hobby); //true
//3.再次修改
boy.hobby=function(){
return 'play basketball';
}
console.log(boy.hobby()); //'play basketball'
console.log(girl.hobby()); //'swimming'上述代码分析
- 2 中此时共享了hobby这个方法
- 3 中 因为对象示例boy拥有hobby方法,就不会在继承原型对象上的hobby方法。
个人额外理解
- 无论什么时候,只要创建了一个新函数,就会根据一组特定的规则为该函数创建一个prototype属性,默认情况下prototype属性会默认获得一个constructor(构造函数)属性
https://blog.csdn.net/cc18868876837/article/details/81211729 这里有关于
__proto__和constructor 以及 prototype的深度理解,以下为总结。- 我们需要牢记两点:
__proto__和constructor属性是对象所独有的;- prototype属性是函数所独有的,因为函数也是一种对象,所以函数也拥有
__proto__和constructor属性
__proto__属性的作用就是当访问一个对象的属性时,如果该对象内部不存在这个属性,那么就会去它的__proto__属性所指向的那个对象(父对象)里找,一直找,直到__proto__属性的终点null,再往上找就相当于在null上取值,会报错。通过__proto__属性将对象连接起来的这条链路即我们所谓的原型链。- prototype属性的作用就是让该函数所实例化的对象们都可以找到公用的属性和方法,即boy.
__proto__=== Person.prototype为 ture。 - constructor属性的含义就是指向该对象的构造函数,所有函数(此时看成对象了)最终的构造函数都指向Function。
- 我们需要牢记两点:
原型链继承
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19fucntion Supper(){
this.supProp = "Supper"
}
Supper.prototype.showSup = function(){
}
fcuntion Sub(){
this.supProp = "Sub"
}
//原型链继承关键
Sub.prototype = new Supper()
Sub.prototype.showSub = funciton(){
}
var sub = new Sub()
//从原型链上去找
sub.showSup()
sub.showSub()
附加
- 当fn去寻找Fn.prototype上定义的函数时,是通过隐式原型链
__proto__去寻找的。 - 实例对象的隐式原型等于构造函数的显式原型
- 当fn去寻找Fn.prototype上定义的函数时,是通过隐式原型链
策略模式
定义:定义一系列算法,将每个算法封装到具有公共接口的一系列策略类中,从而使它们可以相互替换 &并且让算法可在不影响客户端的情况下发生变化
作用:
- 算法可独立于使用外部而变化
- 客户端方便根据外部条件选择不同策略来解决不同问题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57var strategies = {
isNonEmpty: function (value, errorMsg) {
if (value == '') {
return errorMsg
}
},
minLength: function (value, length, errorMsg) {
if (value.length < length) {
return errorMsg
}
},
isMobile: function (value, errorMsg) {
if (!/^1[3|5|8][0-9]{9}$/.test(value)) {
return errorMsg
}
}
}
var validateFunc = function () {
var validator = new Validator()
validator.add(registerForm.username, 'isNonEmpty', '用户名不能为空');
validator.add(registerForm.password, 'minLength:6', '密码长度不能少于6位');
validator.add(registerForm.phonenumber, 'isMobile', '手机号格式不正确');
var errorMsg = validator.start()
return errorMsg
}
var registerForm = document.getElementById('registerform')
registerForm.onsubmit = function () {
var errorMsg = validateFunc()
if (errorMsg) {
alert(errorMsg)
return false
}
}
var Validator = function () {
// 保存校验规则的
this.cache = []
}
Validator.prototype.add = function (dom, rule, errorMsg) {
var ary = rule.split(':')
this.cache.push(function () {
var strategy = ary.shift()
ary.unshift(dom.value)
ary.push(errorMsg)
console.log(ary)
return strategies[strategy].apply(dom, ary)
})
}
Validator.prototype.start = function () {
for (var i = 0, vaFunc; vaFunc = this.cache[i++];) {
var msg = vaFunc()
if (msg) {
return msg
}
}
}
数组的reduce方法
写法
1
2
3
4
5
6
7
8
9array.reduce(function(prev,cur,index,arr){
...
},init)
/*init: 可以没有
*/
//调用写法
array.reduce(fuinction(prev,cur){
},init)参数
- arr 表示原数组
- init 表示初始值
- prev 表示上一次调用回调时返回的值,有初始值init时,第一次就是为初始值init
- cur 表示当前正在处理的数组元素
- index 表示当前正在处理的数组元素的索引,有初始值init,则索引为0,没有索引值则为1
应用
统计数组元素出现的次数
1
2
3
4
5
6
7
8
9
10let strs = ['jacky','tian','ning','jacky','gala','uzi','gala']
let strNum = strs.reduce(function(pre,cur){
if(cur in pre){
pre[cur]++
}else{
pre[cur] = 1
}
return pre
},{})数组扁平化
1
2
3
4
5
6
7
8const nums = [1, [2, [3, [4, 5]]], 6]
const newNums = (nums) => {
return nums.reduce((pre, cur) => {
return pre.concat(Array.isArray(cur) ? newNums(cur) : cur)
}, [])
}
console.log(newNums(nums))
call-apply-bind
写法:
1
2
3fun.call(thisArg, param1, param2, ...)
fun.apply(thisArg, [param1,param2,...])
fun.bind(thisArg, param1, param2, ...)作用:改变this指向
区别:
- apply第二个参数传入得是一个array
- call是第二个参数一个个传入
- bind就是改变了上下文的函数,不执行函数,就是调用的时候返回一个函数。
应用
将伪数组转化为数组
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19obg1 = {
0 : "a",
1 : 'b',
2 : 'c',
length: 3//必须要有
}
Array.prototype.slice.call(obj1)
//dom元素转化为数组
function listToArray(dom){
var ary= []
try{
//IE8以下会报错
ary = Array.prototype.slice.call(dom)
}catch(e){
for(var i = 0; i < dom.length, i++){
ary[ary.length] = dom[i]
}
}
}数组的拼接
1
Array.prototype.push.apply(arr1,arr2)
ES6新特性(未完待续)
基础内容
Let声明变量
- 基础特性:变量不能重复声明、块级作用域、没有变量提升
const声明常量
- 不允许修改
变量结构赋值
- let定义的对象内和属性名和方法名 必须和 对象内的一致
1
2
3
4
5
6
7
8
9
10
11
12
13
14const p = {
name:"zhao",
age:5,
song: function (){
console.log('song')
}
}
let {name,age,song} = p
//可以一一对应,并且直接使用,但是方法命要和对象类的一致
console.log(name)
song()
//下面这种可以直接使用,
let {song} = p
song()模板字符串(反引号 `)
- 可以直接出现换行符
- 可以进行字符串拼接,通过在字符串内加入${变量名}
对象的简化写法
1
2
3
4
5
6let p{
name,
age,
show(){
}
}箭头函数
形式
1
2
3let fn = () =>{
console.log()
}特性
如果只有一个参数,可以不写小括号
如果只有一句代码,且返回了,可以不写花括号,省略return
1
2
3//计算n*n并返回
let fn = n => n*n
fn(8)this指针是静态的,就是调用它所在的作用域的this,且不能改
示例
- 适合于this无关的回调,定时器,数组的方法回调
- 不适合与this有关的回调,比如说,dom事件的回调事件
扩展运算符(… )
- 可以将数组转换为逗号分隔的参数序列
rest参数
可以说是 arguments 的替代版,但是arguments是对象,rest参数是数组
1 | const abc = ['a','b','c'] |
Symbol 数据类型
作用:独一无二的值,对象的属性名现在可以有两种类型,一种是原来就有的字符串,另一种就是新增的 Symbol 类型。
特性:
不能用 new 来进行声明定义
Symbol 值可以显式转为字符串
1
2
3
4let sym = Symbol('My symbol');
String(sym) // 'Symbol(My symbol)'
sym.toString() // 'Symbol(My symbol)'
生成器函数
- yield: 将函数代码分块
未完待续….




