使用 var 定义的变量或者函数表达式会存在变量提升的情况:将变量或者函数的定义提升到代码块的顶部,但不会提升赋值。
console.log(a) // 输出 undefined。原因是使用 var 定义的变量存在变量提升的情况
var a = 10
-----相当于下面的代码-----
var a // 将变量的定义提升到了代码块的顶部
console.log(a)
a = 10
// 块级作用域
for (var i = 0; i < 10; i++) {
var j = i + 1
}
console.log(i, j) // 输出 10, 10
// 换成 let 就会报错 -- 原因:let 存在块级作用域
for (let i = 0; i < 10; i++) {
let j = i + 1
}
console.log(i, j) // 报错
强制类型转换:parseInt()
parseFloat()
toString()
等
隐式类型转换:if、逻辑判断、==、 + 字符串拼接
// 判断传过来的参数是不是一个对象
function isObject(obj) {
return typeof obj === ‘object‘ && obj !== null
}
function isEqual(obj1, obj2) {
// 如果有一个不是对象,那么就直接判断就好了
if (!isObject(obj1) || !isObject(obj2)) {
// 参与 equal 的一般不会是函数
return obj1 === obj2
}
// 如果传递过来的参数是 同一个对象,直接返回 true
if (obj1 === obj2) {
return true
}
// 如果两个都是对象或者数组,而且不是同一个参数
// 1. 先取出 obj1 和 obj2 的 keys ,比较个数
const obj1Keys = Object.keys(obj1)
const obj2Keys = Object.keys(obj2)
if (obj1Keys.length !== obj2Keys.length) {
return false
}
// 2. 以 obj1 为基准,和 obj2 依次递归比较
for (let key in obj1 ) {
// 比较当前 key 的 value --递归
const res = isEqual(obj1[key], obj2[key])
if (!res) {
return false
}
}
// 3. 全相等
return true
}
const obj1 = {
a: 100,
b: { x: 10,y: 20 }
}
const obj2 = {
a: 100,
b: { x: 10,y: 20 }
}
// console.log(isEqual(obj1, obj1))
const arr1 = [1,2,3,4]
const arr2 = [1,2,3,4]
console.log(isEqual(arr1, arr2))
split()
将字符串以某个字符分割成数组
join()
将数组以某个字符组合成一个字符串
‘1-2-3‘.split(‘-‘) // [1,2,3]
[1,2,3].join(‘-‘) // ‘1-2-3‘
参考:https://aurorablog.top/archives/ec6bfcc0.html
功能是什么?
返回值是什么?
是否会对原数组造成影响?
方法 | 功能 | 返回值 | 是否影响原数组 |
---|---|---|---|
pop | 删除数组的最后一个值 | 返回被删除的那个值 | 是 |
push | 在数组最后增加一项 | 返回增加一项后的数组长度 | 是 |
unshift | 在数组最前面增加一项 | 返回增加一项后的数组长度 | 是 |
shift | 删除数组的第一个值 | 返回删除的那个值 | 是 |
数组的 api 有那些事纯函数?
纯函数:1. 不改变原数组(改变原函数有副作用) 2. 返回一个数组
const arr = [1, 2, 3, 4, 5]
// concat
const arr1 = arr.concat([6, 7, 8])
console.log(arr1) // [1, 2, 3, 4, 5, 6, 7, 8]
// map
const arr2 = arr.map(num => num * 10)
console.log(arr2) // [10, 20, 30, 40, 50]
// filter
const arr3 = arr.filter(num => num > 2)
console.log(arr3) // [3, 4, 5]
// slice 参数是索引,截取一个新的数组
const arr4 = arr.slice(1, 3)
console.log(arr4) // [2, 3]
const arr5 = arr.slice()
console.log(arr5) // [1, 2, 3, 4, 5]
非纯函数:
功能区别:
? 英文含义:slice - 切片; splice - 剪接
? slice:截取数组的某一个片段
? splice:剪接原函数的某一部分,返回值是剪接的部分,原函数修改为去除被剪接的部分。
参数和返回值:
? slice()
:如果不传入参数,则相当于将原数组复制了一份;可以接受两个参数,第一个参数是 startIndex ,第二个参数是 endIndex ,左闭右开;如果没有第二个参数则会将 startIndex 后面所有的内容截取。如果想要截取最后几个数,可以写成 slice(-num)
意思是截取最后的 num 个内容。
const arr = [10, 20, 30, 40, 50]
const arr1 = arr.slice() // [10, 20, 30, 40, 50]
const arr2 = arr.slice(1, 4) // [20, 30, 40]
const arr3 = arr.slice(2) // [30, 40, 50]
const arr3 = arr.slice(-2) // [40, 50]
? splice()
:第一个参数是开始的位置,第二个参数的剪接的个数,第三个参数是要替换的元素值。返回值是被剪接的内容。
const arr = [1, 2, 3, 4, 5]
// 返回值 arr :[2, 3] 原数组 arr 变为: [1, ‘a‘, ‘b‘, 4, 5]
const arr1 = arr.splice(1, 2, ‘a‘, ‘b‘)
// 从索引为 1 的位置插入两项内容,arr:[1, "a", "b", 2, 3, 4, 5]
const arr2 = arr.splice(1, 0, ‘a‘, ‘b‘) // 返回值为空
// 从索引为 1 的位置删除两项内容,arr: [1, 4, 5]
const arr3 = arr.splice(1, 2) // 返回值为 [2, 3]
是否是纯函数:
? slice() 是纯函数, splice() 不是纯函数。
map 的参数和返回值:
map() 方法定义在JavaScript的Array中, 它返回一个新的数组, 数组中的元素为原始数组调用函数处理后的值。
array.map(function (currentValue, index, arr), thisIndex).
callback()
? currentValue: 必须。 当前元素的的值。
? index: 可选。 当前元素的索引。
? arr: 可选。 当前元素属于的数组对象。
thisIndex 可选。 对象作为该执行回调时使用, 传递给函数, 用作 "this"的值。
parseInt 的参数和返回值:
parseInt(string, radix) 解析一个字符串并返回指定基数的十进制整数,
radix
是2-36之间的整数,表示被解析字符串的基数。
string:要被解析的字符串,如果不是一个字符串,则将其转换为字符串(使用 ToString
抽象操作)。字符串开头的空白符将会被忽略。
radix:可选。该值介于 2~36 之间。如果省略该参数或其值为 0, 则数字将以 10 为基础来解析。 如果它以“ 0x” 或“ 0X” 开头, 将以 16 为基数。如果该参数小于 2 或者大于 36, 则 parseInt() 将返回 NaN
代码拆解:
[10, 20, 30].map((num,index)=>{
return parseInt(num,index)
})
问题解析:
parseInt方法有两个参数,默认接受了来自map方法的前两个参数,map的前两个参数分别是遍历的值和索引;
所以parseInt接收到的三个组值得情况分别是:
parseInt(10,0):数字基数为0,数字以 10进制解析,故结果为 10;
parseInt(20,1):数字基数为1,数字以 1进制解析,1进制出现了2,1进制无法解析,结果返回NaN;
parseInt(30,2):数字基数为2,数字以 2进制解析,2进制出现了3,3进制无法解析,结果返回NaN;
所以最终结果为:[10, NaN, NaN]。参考 MDN 和 这篇文章
call() 和 apply() 的作用相同,都可以改变 this 的指向,但是参数有所不同。第一个参数都是 作为函数上下文的对象,call() 的第二个参数是一个参数列表;apply() 的第二个参数是一个数组或者类数组。
func.apply(obj, [‘A‘, ‘B‘]);
func.call(obj, ‘C‘, ‘D‘);
用法:
改变 this 指向
var obj = {
name: ‘linxin‘
}
function func() {
console.log(this.name);
}
func.call(obj);
call 方法的第一个参数是作为函数上下文的对象,这里把 obj 作为参数传给了 func,此时函数里的 this 便指向了 obj 对象。此处 func 函数里其实相当于
function func() {
console.log(obj.name);
}
借用别的对象的方法
var Person1 = function () {
this.name = ‘linxin‘;
}
var Person2 = function () {
this.getname = function () {
console.log(this.name);
}
Person1.call(this);
}
var person = new Person2();
person.getname(); // linxin
从上面我们看到,Person2 实例化出来的对象 person 通过 getname 方法拿到了 Person1 中的 name。因为在 Person2 中,Person1.call(this) 的作用就是使用 Person1 对象代替 this 对象,那么 Person2 就有了 Person1 中的所有属性和方法了,相当于 Person2 继承了 Person1 的属性和方法。
调用函数
apply、call 方法都会使函数立即执行,因此它们也可以用来调用函数。
function func() {
console.log(‘linxin‘);
}
func.call();// linxin
call 和 bind 的区别
在 EcmaScript5 中扩展了叫 bind 的方法,在低版本的 IE 中不兼容。它和 call 很相似,接受的参数有两部分,第一个参数是是作为函数上下文的对象,第二部分参数是个列表,可以接受多个参数。
它们之间的区别有以下两点。
bind() 的返回值是函数。
var obj = {
name: ‘linxin‘
}
function func() {
console.log(this.name);
}
var func1 = func.bind(obj);
func1(); // linxin
bind 方法不会立即执行,而是返回一个改变了上下文 this 后的函数。而原函数 func 中的 this 并没有被改变,依旧指向全局对象 window。
参数的使用
function func(a, b, c) {
console.log(a, b, c);
}
var func1 = func.bind(null,‘linxin‘);
func(‘A‘, ‘B‘, ‘C‘); // A B C
func1(‘A‘, ‘B‘, ‘C‘); // linxin A B
func1(‘B‘, ‘C‘); // linxin B C
func.call(null, ‘linxin‘); // linxin undefined undefined
call 是把第二个及以后的参数作为 func 方法的实参传进去,而 func1 方法的实参实则是在 bind 中参数的基础上再往后排。
事件捕获和事件冒泡都是为了解决页面中事件流(事件的执行顺序)的问题。
<div id="outer">
<p id="inner">Click me!</p>
</div>
事件捕获:事件从最外层触发,直到找到最具体的元素。
如上面的代码,在事件捕获下,如果点击p标签,click事件的顺序应该是 document->html->body->div->p
事件冒泡:事件会从最内层的元素开始发生,一直向上传播,直到触发document对象。
因此在事件冒泡下,p元素发生click事件的顺序为 p->div->body->html->document
事件绑定:
js通过 addEventListener
绑定事件。 addEventListener
的第三个参数就是为冒泡和捕获准备的。
addEventListener
有三个参数:
element.addEventListener(event, function, useCapture)
第一个参数是:需要绑定的事件
第二个参数是:触发事件后要执行的函数
第三个参数是:默认值是 false
,表示在 事件冒泡阶段 调用事件处理函数;如果设置参数为 true
,则表示在 事件捕获阶段 调用事件处理函数。
对于事件代理来说,在事件捕获或者事件冒泡阶段处理并没有明显的优劣之分,但是由于事件冒泡的事件流模型被所有主流的浏览器兼容,从兼容性角度来说通常使用事件冒泡模型。
事件代理的好处(为什么要使用事件代理)?
比如100个(甚至更多)li标签绑定事件,如果一个一个绑定,不仅会相当麻烦,还会占用大量的内存空间,降低性能。使用事件代理的作用如下:
事件代理的原理:
事件代理(事件委托) 是利用事件的冒泡原理来实现的,比如当我们点击内部的li标签时,会冒泡到外层的ul,div等标签上。因此,当我们想给很多个li标签添加同一个事件的时候,可以给它的父级元素添加对应的事件,当触发任意li元素时,会冒泡到其父级元素,这时绑定在父级元素的事件就会被触发,这就是事件代理(委托),委托他们的父级代为执行事件。
demo
<ul id="ul1">
<li>111</li>
<li>222</li>
<li>333</li>
<li>444</li>
</ul>
<script>
window.onload = function(){
var oUl = document.getElementById("ul1");
oUl.onclick = function(){
alert(123);
}
}
</script>
封装通用的事件绑定函数
// 通用的事件绑定函数:
function bindEvent(elem,type,selector,fn){
if(fn == null){
fn = selector
selector = null
}
elem.addEventListener(type,event=>{
const target = event.target
if(selector){
//代理绑定
if(target.matches(selector)){
fn.call(target,event)
}else{
//普通绑定
fn.call(target,event)
}
}
})
}
影响:变量会常驻内存,得不到释放。
闭包函数:声明在一个函数中的函数,叫做闭包函数。
闭包:内部函数总是可以访问到其所在的外部函数中,声明的参数和变量,即使是在其外部函数被返回之后。
函数内部定义函数,并且这个内部函数能够访问到外层函数中定义的变量,就叫做闭包
特点
1、让外部访问函数内部变量成为可能。
2、局部变量会常驻在内存中。
3、可以避免使用全局变量,防止全局变量污染。
4、会造成内存泄漏(内存空间长期被占用,而不被释放)
闭包的创建
闭包就是可以创建一个独立的环境,每个闭包里面的环境都是独立的,互不干扰。闭包会发生内存泄漏,每次外部函数执行的时候,外部函数的引用地址不同,都会重新创建一个新的地址。但凡是当前活动对象中又被内部子集引用的数据,那么这个时候,这个数据不删除,保留一根指针给内部活动对象
闭包使用场景:防抖函数
// 阻止默认
event.stopPropagation()
// 阻止默认行为
event.preventDefault()
jsonp 是通过 script 标签来实现跨域的(script 标签默认可以访问外域链接),而 ajax 是通过 XMLHttpRequest 这个 api 实现的。
同源策略:协议、域名、端口号都相同
加载图片 css js 可以无视同源策略
<img src=跨域的图片地址>
如果引用的图片网站做了图片的防盗链,那么浏览器就无法加载这个图片<link href=跨域的css地址>
<script src=跨域的js地址><script>
<img />
可用于统计打点,可使用第三方统计服务<link /> 和 <script>
可使用 CDN,CDN一般都是外域<script>
可实现 JSONPwindow.addEventListener(‘load‘, function() {
// 页面的全部资源加载完才会执行,包括图片、视频等
})
document.addEventListener(‘DOMContentLoaded‘, function () {
// DOM 渲染完成即可执行,此时图片、视频可能没有加载完毕
})
== 会尝试进行类型转换
=== 严格相等
那些场景下用 == :除了 == null
都用 ===
const obj = {x: 10}
if (obj.a == null) {
// 相当于
// if (obj.a === null || obj.a === null) {}
}
函数声明:function fn() {...}
函数表达式:const fn = function () {...}
函数声明会在代码执行前预加载,而函数表达式不会
// 函数声明
var res = sum(10, 20)
console.log(res) // 30
function sum(num1, num2) {
return num1 + num2
}
----------------------
// 函数表达式
var res = sum(10, 20)
console.log(res) // 报错:sum 不是一个函数
var sum = function(num1, num2) {
return num1 + num2
}
Object.create() 创建一个空对象,Object.create(obj) 把创建的空对象的原型设置为 obj
/^[a-zA-Z]\w{5, 29}$/
let i
for (i = 1; i <=3; i++) {
setTimeout(function() { // 异步代码
console.log(i)
}, 0)
}
// 输出:4 4 4
let a = 100
function test() {
alert(a)
a = 10
alert(a)
}
test() // 100 10
alert(a) // 10
String.prototype.trim = funciton() {
return this.replace(/^\s+/, ‘‘).replace(/\s+$/, ‘‘)
}
function max() {
nums = Array.prototype.slice.call(arguments) // 变为数组
let max = 0
nums.forEach(num => {
if (num > max) {
max = num
}
});
return max
}
max(1, 2, 3, 4, 5) // 5
使用 Math.max() 方法
try {
// todo
} catch(ex) {
// 手动捕获异常
console.log(ex)
} finally {
// todo
}
// 自动捕获
window.onerror = function (message, source, lineNum, colNum, error) {
// 1. 对于跨域的 js ,如 cdn 不会有详细的报错信息
// 2. 对于压缩的 js ,还要配合 sourceMap 反查到未压缩代码的行和列
}
// 传统方式
function query(name) {
const search = location.search.substr(1)
// search: ‘a=10&b=20&c=30‘
const reg = new RegExp(`(^|&)${name}=([^&*])(&|$)`, ‘i‘)
const res = search.match(reg)
console.log(res)
if (res === null) {
return null
}
return res[2]
}
query(‘a‘) // 10
// URLSearchParams
function query(name) {
const search = location.search
const p = new URLSearchParams(search)
return p.get(name)
}
console.log(query(‘a‘)) // 10
// 传统方式,分析 search
function queryToObj() {
const res = {}
const search = location.search.substr(1) // 去除 ?
search.split(‘&‘).forEach( paramsStr => {
const arr = paramsStr.split(‘=‘)
const key = arr[0]
const value = arr[1]
res[key] = value
})
return res
}
// URLSearchParams
function queryToObj() {
const res = {}
const paramsList = new URLSearchParams(location.search)
paramsList.forEach((val, key) => {
res[key] = val
})
return res
}
function flat(arr) {
// 判断 arr 中有没有深层数组
const isDeep = arr.some(item => item instanceof Array)
// 如果没有深层数组,直接返回
if (!isDeep) {
return arr
}
const res = Array.prototype.concat.apply([], arr)
return flat(res) // 递归
}
const res = flat( [1, 2, [10, 20, [300,400]]] )
console.log(res)
function unique(arr) {
const res = []
arr.forEach(item => {
if (res.indexOf(item) < 0) {
res.push(item)
}
})
return res
}
const res = unique([1,1,,1,1,2,3,3,3])
console.log(res)
// 使用 Set
function unique(arr) {
return new Set(arr)
}
const res = unique([1, 1, 2, 3, 3, 2])
console.log(res)
function deepClone(obj) {
if ( typeof obj !== ‘object‘ || obj == null) {
// 如果 obj 不是对象,或者 obj 为 null 或者 undefined,直接返回
return obj
}
let result
if (obj instanceof Array) {
result = []
} else {
result = {}
}
for (let key in obj) {
// 保证 key 不是原型的属性
if (obj.hasOwnProperty(key)) {
result[key] = deepClone(obj[key])
}
}
// 返回结果
return result
}
const res = deepClone({name: ‘aurora‘, age: 21, address: {province: ‘heilongjiang‘, city: ‘haerbin‘}})
console.log(res)
Object.assign() 不是深拷贝!!
<style>
#div1 {
width: 100px;
height: 50px;
background-color: red;
}
</style>
<div id=‘div1‘></div>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script>
// 3s 把宽度从 100px 变为 640px, 即增加 540px
// 60帧/s 3s 180帧 每次增加 3px
const $div = $(‘#div1‘)
let curWidth = 100
const maxWidth = 640
// function animation() {
// curWidth = curWidth + 3
// $div1.css(‘width‘, curWidth)
// if (curWidth < maxWidth) {
// // 自己控制时间
// setTimeout(animation, 16.7)
// }
// }
function animation() {
curWidth = curWidth + 3
$div1.css(‘width‘, curWidth)
if (curWidth < maxWidth) {
window.requestAnimationFrame(animation)
}
}
animation()
</script>
原则:多使用内存、缓存,减少计算、减少网络请求
方向:加载页面,页面渲染,页面操作流畅度
原文:https://www.cnblogs.com/aurora-ql/p/13657820.html