JS知识库
# 基础知识点
# 正则表达式
需求一:删除开头和结尾的空白
思路,找到空白,然后干掉它
let hello = " Hello, World! ";
// 注意要全局匹配,不然会寄
let wsRegex = /^\s+|\s+$/g;
let result = hello.replace(wsRegex,"");
需求二:限制可能的用户名
规则:
1.用户名只能是数字字母字符。
2.用户名中的数字必须在最后。 数字可以有零个或多个。 用户名不能以数字开头。
3.用户名字母可以是小写字母和大写字母。
4.用户名长度必须至少为两个字符。 两位用户名只能使用字母。
let username = "JackOfAllTrades";
let userCheck = /^[a-z][a-z]+\d*$|^[a-z]\d\d+$/i;
let result = userCheck.test(username);
# ES6语法
使用箭头函数编写简洁的匿名函数
有很多情况我们只会使用一次这个函数,那么我们就可以编写一个匿名函数
const myFunc = function(){
//myFunc是常量名,不是函数名
const myVar = "value";
return myVar;
}
ES6 提供了其他写匿名函数的方式的语法糖。 你可以使用箭头函数:
const myFunc = () => {
const myVar = "value";
return myVar;
}
如果只返回一个值的话还有简化的写法
const myFunc = () => "value";
- 箭头函数可以传参用参
- 箭头函数可以给参数设置默认值
rest操作符
...args
展开操作符可以用于创建有一个变量来接受多个参数的函数。 这些参数被储存在一个可以在函数内部读取的数组中。
计算数组的最大值
way1
var arr = [6,89,3,45];
var maximus = Math.max.apply(null,arr);
console.log(maximus);
// 89
way2
var arr = [6,89,3,45];
var maximus = Math.max(...arr);
console.log(maximus);
// 89
使用解构赋值来获取对象的值
// ES5语法
const user = { name: 'John Doe', age: 34 };
const name = user.name;
const age = user.age;
// ES6语法
const {name,age} = user;
还可以解构嵌套对象中的值。
// ES5
const user = {
johnDoe: {
age: 34,
email: 'johnDoe@freeCodeCamp.com'
}
};
这是解构对象的属性值赋值给具有相同名字的变量:
// ES6
const {johnDoe:{age,email}} = user;
这是将对象的属性值赋值给具有不同名字的变量:
const {johnDoe:{age:userAge,email:userEmail}} = user;
使用解构赋值从数组中分配变量
选择元素来给变量赋值
const [a,b] = [1,2,3,4,5,6];
console.log(a,b);
// 1 2
通过逗号分隔符获得自己想要的值
const [a,b,,,c] = [1,2,3,4,5,6];
console.log(a,b,c);
// 1 2 5
使用解构赋值配合 rest 操作符来重新分配数组元素
const [a,b,...arr] = [1,2,3,4,5,7];
console.log(a,b);
console.log(arr);
// 1,2
// [3,4,5,7]
使用简单字段编写简洁的对象字面量声明
// ES5
const getMousePosition = (x,y) => ({
x:x,
y:y
});
// ES6
const getMousePosition = (x,y) => ({x,y});
导出与导入
- 直接用export在文件里导出
- 导入所有文件
import * as balabala from '路径'
,把balabala作为一个总函数就好了 - 用 export default 创建一个默认导出
Promise
Promise 是异步编程的一种解决方案 - 它在未来的某时会生成一个值。 任务完成,分执行成功和执行失败两种情况。
const myPromise = new Promise((resolve, reject) => {
});
Promise 有三个状态:pending、fulfilled 和 rejected。 上一个挑战里创建的 promise 一直阻塞在 pending 状态里,因为没有调用 promise 的完成方法。 Promise 提供的 resolve 和 reject 参数就是用来结束 promise 的。 Promise 成功时调用 resolve,promise 执行失败时调用 reject.
Promise 的then方法
// result即为传入resolve的参数
myPromise.then(result => {
});
使用 catch 处理 Promise 失败的情况
当 promise 失败时会调用 catch 方法。 当 promise 的 reject 方法执行时会直接调用。
myPromise.catch(error => {
});
# this相关问题
运行时this永远指向最后调用它的那个对象。
谁new了那个对象,this就指向谁
在其他函数内部定义立即执行函数,this默认window
this的基本绑定
function foo() {
console.log(this.a)
}
var a = 1
foo()
const obj = {
a: 2,
foo: foo
}
obj.foo()
const c = new foo()
1.对于直接调用 foo 来说,不管 foo 函数被放在了什么地方,this 一定是 window
2.对于 obj.foo() 来说,我们只需要记住,谁调用了函数,谁就是 this,所以在这个场景下 foo 函数中的 this 就是 obj 对象
3.对于 new 的方式来说,this 被永远绑定在了 c 上面,不会被任何方式改变 this
4.不管我们给函数 bind 几次,fn 中的 this 永远由第一次 bind 决定
this的顺序规则:
1.函数是否在new中调用(new绑定)?如果是的话this绑定的是新创建的对象。
var bar = new foo();
2.函数是否通过call、apply(显式绑定)或者硬绑定调用?
var bar = foo.call(obj2);
3.函数是否在某个上下文对象中调用(隐式绑定)?如果是的话,this绑定的是那个上下文对象。
var bar = obj1.foo();
4.如果都不是的话,使用默认绑定。如果在严格模式下,就绑定到undefined,否则绑定到全局对象。
var bar = foo();
bind(),apply(),call()
call()方法与apply()方法返回的结果是完全相同的,至于是使用 apply()还是 call(),完全取决于你采取哪种给函数传递参数的方式最方便。
- 参数数量/顺序确定就用call,参数数量/顺序不确定的话就用apply。
- 考虑可读性:参数数量不多就用call,参数数量比较多的话,把参数整合成数组,使用apply。
bind()方法会创建一个函数的实例,其 this 值会被绑定到传给 bind()函数的值。意思就是 bind() 会返回一个新函数。
# DOM核心
# 关于DOM操作,主要有创建,增,删,改,查,属性操作,事件操作。
# (1)创建
1.document.write
2.innerHTML
3.createElement
State:
1.document.write 是直接将内容写入页面的内容流,但是文档流执行完毕会导致页面全部重绘,只保留html标签和写入内容。
2.innerHTML 是将内容写入某个DOM节点,不会导致页面全部重绘。
3.innerHTML 创建多个元素效率更高(不要拼接字符串,采取数组形式拼接),结构稍微复杂。
innerHTML在不使用拼接字符串的情况下,比创造元素拼接元素要快一些。 4.虽然createElement()创建多个元素慢一些,但是结构更清晰。
# (2)增
1.appendChild
// 此方法在node节点「父亲节点」里,顺序增加一个节点
node.appendChild(child)
2.insertBefore
// 此方法在node节点「父亲节点」里,在开头增加一个节点
node.insertBefore()
# (3)删
1.removeChild
//此方法从DOM中删除一个子节点,返回删除的节点。
node.removeChild()
# (4)改
主要修改dom的元素属性,dom的内容,属性,表单的值等
修改内容 | index |
---|---|
元素属性 | src,href,title |
普通元素内容 | innerHTML,innerText |
元素样式 | style,className |
表单内容 | value,type,disabled |
# (5)查
1.DOM提供的API方法:getElementById,getElementByTagName(古老用法)
2.H5提供的新方法:querySelector,querySelectorAll
3.利用节点操作获取元素:父(parentNode),子(children),兄(previousElementSibling,nextElementSibling)
# (6)属性操作
1.setAttribute:设置dom的属性值 2.getAttribute:得到dom的属性值 3.removeAttribute移除属性
# (7)事件操作
鼠标事件 | 触发条件 |
---|---|
onclick | 鼠标点击触发 |
onmouseover | 鼠标经过触发 |
onmouseout | 鼠标离开触发 |
onfocus | 获得鼠标焦点触发 |
onblur | 失去鼠标焦点触发 |
onmousemove | 鼠标移动触发 |
onmouseup | 鼠标谈起触发 |
onmousedown | 鼠标按下触发 |
# BOM核心
元素偏移量 offset 系列
概述:offset 翻译过来就是偏移量, 我们使用 offset 系列相关属性可以动态的得到该元素的位置(偏移)、大小等。
(1)获得元素距离带有定位父元素的位置
(2)获得元素自身的大小(宽度高度)
(3)注意:返回的数值都不带单位
offset系列属性 | 作用 |
---|---|
element.offsetParent | 返回作为该元素带有定位的父级元素 如果父级没有定位则返回body |
element.offsetTop | 返回元素相对带有定位父元素上方的偏移 |
element.offsetLeft | 返回元素相对带有定位父元素左方的偏移 |
element.offsetWidth | 返回自身包括padding,边框,内容区的宽度,返回数值不带单位 |
element.offsetHeight | 返回自身包括padding,边框,内容区的宽度,返回数值不带单位 |
offset与style区别
offset | style |
---|---|
offset 可以得到任意样式表中的样式值 | style 只能得到行内样式表中的样式值 |
offset 系列获得的数值是没有单位的 | style.width 获得的是带有单位的字符串 |
offsetWidth 包含padding+border+width | style.width 获得不包含padding和border 的值 |
offsetWidth 等属性是只读属性,只能获取不能赋值 | style.width 是可读写属性,可以获取也可以赋值 |
所以,我们想要获取元素大小位置,用offset更合适 | 所以,我们想要给元素更改值,则需要用style改变 |
元素可视区 client 系列
概念:client 翻译过来就是客户端,我们使用 client 系列的相关属性来获取元素可视区的相关信息。通过 client 系列的相关属性可以动态的得到该元素的边框大小、元素大小等。
client系列属性 | 作用 |
---|---|
element.clientTop | 返回元素上边框的大小 |
element.clientLeft | 返回元素左边框的大小 |
element.clientWidth | 返回自身包括padding,内容区,不包括边框的宽度,返回数值不带单位 |
element.clientHeight | 返回自身包括padding,内容区,不包括边框的宽度,返回数值不带单位 |
元素可视区 scroll 系列
scroll系列属性 | 作用 |
---|---|
element.scrollTop | 返回被卷去的上侧距离,返回数值不带单位 |
element.scrollLeft | 返回被卷去的左侧距离,返回数值不带单位 |
element.scrollWidth | 返回自身实际的宽度,不含边框,返回数值不带单位 |
element.scrollHeight | 返回自身实际的宽度,不含边框,返回数值不带单位 |
offset/client/scroll的主要用法
1.offset系列 经常用于获得元素位置 offsetLeft offsetTop
2.client 经常用于获取元素大小 clientWidth clientHeight
3.scroll 经常用于获取滚动距离 scrollTop scrollLeft
4.注意页面滚动的距离通过 window.pageXOffset 获得
触屏事件概述
触摸事件对象(TouchEvent)
TouchEvent 是一类描述手指在触摸平面(触摸屏、触摸板等)的状态变化的事件。这类事件用于描述一个或多个触点,使开发者可以检测触点的移动,触点的增加和减少,等等
touchstart、touchmove、touchend 三个事件都会各自有事件对象。
触摸事件对象重点我们看三个常见对象列表:
因为平时我们都是给元素注册触摸事件,所以重点记住 targetTocuhes
移动端拖动元素
[1]touchstart、touchmove、touchend 可以实现拖动元素
[2]但是拖动元素需要当前手指的坐标值 我们可以使用 targetTouches[0] 里面的pageX 和 pageY
[3]移动端拖动的原理:手指移动中,计算出手指移动的距离。然后用盒子原来的位置 + 手指移动的距离
[4]手指移动的距离:手指滑动中的位置 减去 手指刚开始触摸的位置
拖动元素三步曲:
(1)触摸元素 touchstart: 获取手指初始坐标,同时获得盒子原来的位置
(2)移动手指 touchmove: 计算手指的滑动距离,并且移动盒子
(3)离开手指 touchend:
注意: 手指移动也会触发滚动屏幕所以这里要阻止默认的屏幕滚动 e.preventDefault();
# 奇奇怪怪的bug
- 使用
if else if else
这类条件语句的时候,如果你用了=
而不是==
,恭喜你,你寄了。
一般来说
=
会给你判断正确,不会走到后面的else里面。
JavaScript会把大部分的值都视为true,除了所谓的“falsy值”,即false,0,""(空字符串),NaN,null
typeof返回的是字符串!!!
st[i][0].toUpperCase()
会全部大写,需要把后面的一部分操作一下
# 《你不知道的JavaScript》读书笔记
let,var and const
1.let(1)let 如果是在作用块中定义的变量,不会提升「如果先用后定义会报错,undefined」,并且作用域也不变。
(2)let不可以重复声明,否则会报错;var则可以。
建议:写代码的时候加一对花括号来“显式”标明这个作用块
2.var 如果是在作用块中定义的变量,会提升到该作用块的开头部分「但只会提前声明而不会提前赋值」,同时作用域上升一级。
3.const 定义一个变量,声明变量时必须初始化变量,同时const也不能被重复声明
如果这个量被改变会报错
不过如果const声明的是一个对象,那改变对象里面的值并不算修改了这个对象,所以可以,也并不会报错。
IFFE:立即执行函数
IFFE(Immediately Invoked Function Expression):立即执行函数
在书上看到的内容巧合的出现在了JavaScript的专业课程上,有种不期而遇的幸福感
立即执行函数有两种执行方式:
1.(fun{….}());
2.(fun{….})();
好处:不会污染全局作用域
LHS and RHS
1.LHS:赋值操作的目标是谁「左侧」2.RHS(retrieve his source value):谁是赋值操作的源头「非左侧」
提升
var a = 2;
这段代码可以拆分成
var a
与
a = 2
两个语句。
第一个语句自动提升到该作用域的最顶部,
而第二个语句保留在原位置:“使用var时,没有一个一个变量是未声明的”
1.函数提升优先于变量提升,函数提升会把整个函数挪到作用域顶部,变量提升只会把声明挪到作用域顶部
2.var 存在提升,我们能在声明之前使用。let、const 因为暂时性死区的原因,不能在声明前使用
3.var 在全局作用域下声明变量会导致变量挂载在 window 上,其他两者不会
let 和 const 作用基本一致,但是后者声明的变量不能再次赋值
闭包
理解:闭包使得函数可以从外部访问定义时的词法作用域。
识别闭包:
当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行。
典型例子:
function foo() {
var a = 2;
function bar() {
console.log( a );
}
return bar;
}
var baz = foo();
baz();//console.log(a) ==> 2
块作用域与闭包结合
需求:从1打印到5
代码1:
for(var i = 1 ; i <= 5; i++){
(function(){
setTimeout(function timer(){
console.log( i );
},i*1000);
})();
}
// 6 6 6 6 6
原因:
一直输出6是因为i是共用的,执行完了五次循环之后,创建了五个事件之后,i=6
代码2:
for(var i = 1 ; i <= 5; i++){
(function(j){
// var j = i;
setTimeout(function timer(){
console.log( j );
},j*1000);
})( i );
}
//1 2 3 4 5
原因:
每次创建这个函数的时候都有一个传入值,不会复用i
代码3:
for(let i = 1 ; i <= 5; i++){
(function(){
setTimeout(function timer(){
console.log( i );
},i*1000);
})();
}
// 1 2 3 4 5
原因:
每次创建这个函数的时候let都创建了一个块作用域,i是小的作用域里的,已经覆盖掉了原本的i,所以没问题
# JS知识点每日一题
# JS原始数据类型有哪些?引用数据类型有哪些?
# 七大原始值
1.boolean
2.symbol
3.null
4.undefined
5.string
6.number
7.bigint
# 引用数据类型
对象:Object(包含普通对象-Object,数组对象-Array,正则对象-RegExp,日期对象-Date,数学函数-Math,函数对象-Function)
# this的指向问题
# this隐式绑定的情况
1.全局上下文
2.直接调用函数
3.对象.方法的形式调用
4.DOM事件绑定(特殊)
5.new构造函数绑定
6.箭头函数
# 全局上下文
全局上下文默认this指向window, 严格模式下指向undefined。
# 直接调用函数
直接赋值然后调用
See More
let obj = {
zzk: function() {
console.log(this);
}
}
let func = obj.zzk;
func();
直接调用this相当于全局上下文
# 对象.方法的形式调用
obj.zzk();
这就是对象.方法的的情况,this指向对象
# DOM事件绑定
onclick和addEventerListener中 this 默认指向绑定事件的元素。
IE比较奇异,使用attachEvent,里面的this默认指向window。
# new+构造函数
此时构造函数中的this指向实例对象。
# 箭头函数?
箭头函数没有this, 因此也不能绑定。里面的this会指向当前最近的非箭头函数的this,找不到就是window(严格模式是undefined)。比如:
See More
let obj = {
a: function() {
let do = () => {
console.log(this);
}
do();
}
}
obj.a();
// 找到最近的非箭头函数a,a现在绑定着obj, 因此箭头函数中的this是obj
优先级: new > call、apply、bind > 对象.方法 > 直接调用。
# 分析实例
eg1:
See More
function a(){
function b(){
console.log(this);
function c() {
"use strict";
console.log(this);
}
c();
}
b();
}
a();
//输出为 Winodw和undefined;
//执行顺序是首先调用a()方法,然后嵌套着调用b()方法,此时console.log(this)为全局--> Window
//调用b()方法的时候又会调用c()方法,在function c()中使用了严格模式,在严格模式中默认this为undefined
//其实就是第一个是全局,第二个是严格模式的默认值
eg2:
See More
var name = '小白';
function special() {
console.log('姓名:'+this.name);
}
var girl = {
name:'zzk',
detail: function () {
console.log('姓名:'+this.name);
},
woman: {
name:'cxz',
detail: function(){
console.log('姓名:'+this.name);
},
},
special:special,
}
girl.detail();
//ans:zzk
//这是对象.方法的情况
//this指向这个对象
girl.woman.detail();
//ans:cxz
//看后面两个,还是对象.方法的情况,this指向这个对象
girl.special();
//ans:zzk
//这是对象.方法的情况
//this指向这个对象
# 原始类型有哪几种?null 是对象嘛?
答:
原始数据类型有6种,分别是:“Symbol,number,String,null,undefined,boolean.”
用typeof来查看null的话,显示的确实是Object,但其实这是历史遗留的Bug。
在 JS 的最初版本中使用的是 32 位系统,为了性能考虑使用低位存储变量的类型信息,
000 开头代表是对象,然而 null 表示为全零,所以将它错误的判断为 object
# 浅拷贝与深拷贝
(1).浅拷贝:浅拷贝是创建一个新对象,这个对象有原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址,所以如果其中一个对象改变了这个地址,将会影响到另一个对象。
(2).深拷贝是将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象。