个人博客:xiaolan1.icu
1.预编译
规律一:任何变量,如果未经声明就赋值,此变量是属于 window 的属性,而且不会做变量提升。(注意,无论在哪个作用域内赋值)
比如说,如果我们直接在代码里写 console.log(a)
,这肯定会报错的,提示找不到 a
。但如果直接写 a = 100
,这就不会报错,此时,这个 a
就是 window.a
规律二:一切声明的全局变量,全是window的属性。(注意,是全局作用域内声明的全局变量,不是局部变量)
比如说,当我定义 var a = 200
时,此时这个 a
就是 window.a
。
function 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=654
}
console.log(d) //undefined
console.log(b) //undefined
var b = function (){ }
console.log(b) //function (){ }
function c(){ }
console.log(c) //function c(){ }
}
fn(1,2)
预编译
做法:
1.创建AO对象
2.找形参和变量的声明,作为AO对象的属性名,值为undefined
3.实参和形参相统一
4.找函数声明,会覆盖变量的声明
AO:{
a:undefined 1 function a(){ }
c:undefined 2 function c(){ }
d:undefined
b:undefined
}
预编译后函数按执行顺序开始执行
2.作用域
1.全局作用域
(1)全局作用域在页面打开时被创建,关闭时被销毁
(2)编写script标签中的变量和函数,作用域为全局,在页面任意位置都可以访问到
(3)在全局作用域中有全局对象window,由浏览器创建,可以直接调用
(4)全局作用域中声明的变量和函数会作为window对象的属性和方法保存
2.函数作用域
(1)调用函数时,函数作用域被创建,调用结束后被销毁
(2)每调用一次函数就会创建一个新的函数作用域,他们之间是相互独立的
(3)在函数作用域中可以访问全局作用域的变量,在函数外无法访问函数作用域中的变量
(4)在函数作用域中访问变量、函数时,会先在自身作用域中寻找,没找到的话回到函数的上一级作用域去寻找,直到全局作用域
作用域—先预编译,参照 前面所讲的1.预编译
3.哪些操作会造成内存泄漏?
1.闭包
2.意外的全局变量
3.没被清除的定时器
4.脱离dom的引用 :获取了dom元素,dom在页面中被清楚了,但这个引用依然存在
4.闭包以及单例模式
概念:一个普通函数function,如果他可以访问外层作用域的自由变量,这个函数就是一个闭包
广义:JavaScript中的函数都是闭包
狭义:JavaScript中的一个函数,如果访问了外层作用域的变量,那么它是一个闭包
var aa= 666
function a() { //定义函数a,生成函数a外的作用域链
var aa = 123
return function b() {
var bb = 231
console.log(aa);
}
}
let res= a() //执行函数a,产生函数a内的作用域链,函数a中定义函数b,即函数b产生函数b外的作用域链,被保存在了内存中,
res() //输出123 虽然函数a的作用域链被销毁了,但函数a返回函数b,函数b外的作用域链保存在了内存中,这里执行了函数b,函数b随之产生函数b内的作用域链,因为函数b中未定义aa,所以返回上一级作用域链去查找aa,aa=123,输出123;若函数a中未定义aa,则再返回上一级作用域链中查找aa,直到返回到全局作用域链中查找aa,这里输出666
//es5单例模式,body中有一个<button id="button">登陆</button>
var createLogin = function () {
var div = document.createElement('div')
div.innerHTML = '123456'
document.body.append(div)
return div
}
var getSingle = function (fn) {
var result;
return function () {
return result || (result = fn.apply(this, arguments))
}
}
var create = getSingle(createLogin)
document.querySelector('#button').addEventListener('click', function () {
var loginLay = create()
loginLay.style.display = 'block'
}) //点击登陆,会出现一次123456,再次点击不会产生变化
//es6,class类来实现单例模式,不会用到闭包
//静态方法 static
class Foo{
static classMethod(){
return 'hello'
}
}
let foo = new Foo()
console.log(Foo.classMethod()) //hello
console.log(foo.classMethod()) //foo.classMethod is not a function
//实现单例
class xiaoLan{
constructor(name,age,sex){
this.name=name
this.age = age
this.sex = sex
}
static getInstance(name,age,sex){
if(!this.instance){
this.instance = new xiaoLan(name,age,sex)
}
return this.instance
}
}
let xiaolan = xiaoLan.getInstance('小蓝',20,'男')
let xiaolan1 = xiaoLan.getInstance('小蓝',20,'男')
console.log(xiaolan===xiaolan1) //true
5. arguments对象
arguments是类数组对象
注意:箭头函数没有arguments对象
function get(){
console.log (arguments)
}
get(1,2,3) //Arguments(3) [1, 2, 3, callee: ƒ, Symbol(Symbol.iterator): ƒ]
//转化成数组
function getArr(){
console.log([...arguments])
}
getArr(1,2,3) //[1,2,3]
6.数组扁平化处理
const arr = [1, [2, [3, [4, [5, 6], 7], 8], 9]]
//利用ES10新增的flat方法
let arr1 = arr.flat(Infinity)
console.log(arr1) //[1,2,3,4,5,6,7,8,9],底下的输出参照这个
//利用正则表达式+数组的方法
let arr2 = JSON.stringify(arr).replace(/\[|\]/g, '')
arr2 = arr2.split(',').map((item, index) => {
return parseInt(item)
})
console.log(arr2)
//利用正则表达式与JSON.parse
let arr3 = JSON.parse(
'[' + JSON.stringify(arr).replace(/\[|\]/g, '') + ']'
)
console.log(arr3)
//利用递归
let arr4 = []
let fn = (arr) => {
for (let i = 0; i < arr.length; i++) {
if (Array.isArray(arr[i])) {
fn(arr[i])
} else {
arr4.push(arr[i])
}
}
}
fn(arr)
console.log(arr4)
//利用数组的reduce方法+递归
let fnc = (arr) => {
return arr.reduce((pre, cur) => {
return pre.concat(Array.isArray(cur) ? fnc(cur) : cur)
}, [])
}
let arr5 = fnc(arr)
console.log(arr5)
7.对象函数中this指向以及this的绑定规则
this的绑定和定义的位置没有关系,跟函数调用的方式以及调用的位置有关
this是在运行时被绑定的
this的绑定规则:
①默认绑定 :独立的函数调用(函数没有被绑定到某个对象上进行调用)
②隐式绑定 :通过某个对象进行调用
③显示绑定:call,apply,bind在执行函数时,可以明确的绑定this
④new绑定:使用new关键字来调用函数
规则优先级:
①默认绑定规则优先级最低
②显示绑定优先级高于隐式绑定
③new绑定优先级高于隐式绑定
④new 关键字不能和apply,call一起使用,new绑定优先级高于显示绑定
总结:new绑定 >显示绑定(apply/call/bind) >隐式绑定 (obj.foo()) >默认绑定(独立函数调用)
var name = 222
var a = {
name: 111,
say: function(){
console.log(this.name)
}
}
var fun = a.say
fun() //fun.call(window) 此时this指向window,输出222 (默认绑定)
a.say() //a.say.call(a) 此时this指向a,输出111 (隐式绑定)
var b ={
name: 333,
say: function(fun){
fun()
}
}
b.say(a.say) //直接看b中say这个函数 fun()相当于fun.call(window),此时this指向window,输出 222
b.say = a.say
b.say() //b.say.call(b),此时this指向b,输出333 (隐式绑定)
function sum(num1,num2){
console.log( num1+num2,this)
}
//显示绑定
sum.call('asd',20,30) //50 String{'asd'}
sum.apply('asd',[20,30]) //50 String{'asd'}
var newSum = foo.bind('asd',20,30)
newSum() //50 String{'asd'}
//默认绑定和显示绑定冲突(显示绑定优先)
function foo(){
console.log(this)
}
var newFoo = foo.bind('aaa')
newFoo() //String{'aaa'}
//我们通过一个new关键字调用一个函数时(构造器),这时候this是在调用这个构造器时创建出来的
//this = 创建出来的对象
function Person(name,age){
this.name = name
this.age = age
}
var p =new Person('xiaolan',20) //new绑定
console.log(p.name,p.age) //xiaolan 20
//显示优先级高于隐式示例
var obj = {
name: 'obj',
foo: function(){
console.log(this)
}
}
obj.foo().call('abc') //String{'abc'}
//bind的优先级高于隐式绑定
function foo(){
console.log(this)
}
var obj = {
name: 'obj',
foo: foo.bind('aaa')
}
obj.foo() //String{'aaa'}
//new绑定优先级高于隐式绑定示例
var obj = {
name: 'obj',
foo: function (){
console.log(this)
}
}
var f = new obj.foo() // foo {}
//new绑定优先级高于显示绑定示例
function foo(){
console.log(this)
}
var bar = foo.bind('aaa')
var obj = new bar() // foo {}
8.箭头函数以及箭头函数中this指向
箭头函数不会绑定this(可以使用this)、arguments属性
箭头函数不能作为构造函数来使用,即不能和new一起来使用
箭头函数不使用上述所说的this的绑定规则,而是根据外层作用域来决定this
//如果一个箭头函数,只有一行代码,并且返回一个对象,在箭头函数执行的{}外加上一对()
var bar = () => ({ name: 'xlz', age: 20})
//测试箭头函数中this的指向
var foo = () => {
console.log(this)
}
foo() //window
var obj = {foo: foo}
obj.foo() //window
foo.call('abc') //window
var x = 11;
var obj = {
x: 22,
say: () =>{
console.log(this.x);
}
}
obj.say(); //因为箭头函数,不满足上面this的指向,此时this指向上一级为window,输出 11
var obj = {
birth: 2000,
getAge: function(){
var b = this.birth; //2000
var fn = () => new Date().getFullYear() - this.birth;
return fn()
}
};
console.log(obj.getAge());//21,此处this指向它的上层作用域为obj
9. this的面试题
//面试题1
var name ='window'
var person1 = {
name: 'person1',
foo1: function(){
console.log(this.name)
},
foo2: () => console.log(this.name),
foo3: function (){
return function (){
console.log(this.name)
}
},
foo4: function() {
return () => {
console.log(this.name)
}
}
}
var person2 = { name:'person2'}
person1.foo1() //person1(隐式绑定)
person1.foo1.call(person2) //person2(显示绑定优先级大于隐式绑定)
person1.foo2() //window(不绑定作用域,上层作用域是全局)
person1.foo2.call(person2) //同上
person1.foo3()() //window(独立函数调用)
person1.foo3.call(person2)() //window(独立函数调用)
person1.foo3().call(person2) //person2(最终调用返回函数时,用显示绑定)
person1.foo4()() //person1(箭头函数不绑定this,上层作用域this 是person1)
person1.foo4.call(person2)() //person2(上层作用域被显示的绑定了一个person2)
person1.foo4().call(person2) //person1(作用域上层找到person1,与call无关) 在箭头函数中,call,apply中得到对象没用,直接不看
//面试题2
var name = 'window'
function Person (name) {
this.name = name
this.foo1 = function() {
console.log(this.name)
}
this.foo2 = () => console.log(this.name)
this.foo3 = function (){
return function() {
console.log(this.name)
}
}
this.foo4 = function (){
return () =>{
console.log(this.name)
}
}
}
var person1 = new Person('person1')
var person2 = new Person('person2')
person1.foo1() //person1
person1.foo1.call(person2) //person2(显示优先级高于隐式绑定)
person1.foo2() //person1 (上层作用域中的this是person1)
person1.foo2.call(person2) //person1,call里的person2没用,不绑定this
person1.foo3()() //window
person1.foo3.call(person2) //window
person1.foo3().call(person2) //person2
person1.foo4()() //person1
person1.foo4.call(person2)() //person2
person1.foo4().call(person2) //person1 下面这六个跟面试1题的最后六个分析方法一样
//面试题3
var name = 'window'
function Person (name){
this.name = name
this.obj = {
name: 'obj',
foo1: function (){
return function(){
console.log(this.name)
}
},
foo2: function (){
return () => {
console.log(this.name)
}
}
}
}
var person1 = new Person('person1')
person1.obj.foo1()() //window
person1.obj.foo1.call(person2)() //window
person1.obj.foo1().call(person2) //person2
person1.obj.foo2()() //obj
person1.obj.foo2.call(person2)() //person2
person1.obj.foo2().call(person2) //obj
10. call和apply的区别
传递参数不同
function sum(num1,num2){
console.log( num1+num2,this)
}
sum.call('asd',20,30) //第一个参数指向调用的对象,后面的参数挨个写进去
sum.apply('asd',[20,30]) //第一个参数指向调用的对象,后面的参数写进一个数组进行传参
11.赋值、浅拷贝和深拷贝
赋值:把一个对象赋值给一个新的变量时,赋的是该对象在栈中的地址,不是堆中的数据。因此两个对象指向的是同一个存储空间,无论哪个对象发生改变,其实都是改变的存储空间的内容,因此,两个对象是联动的
浅拷贝:重新在堆中创建内存,拷贝前后对象的基本数据类型互不影响,但拷贝前后对象的引用类型共享同一块内存,会相互影响
深拷贝:从堆内存中开辟一个新的区域存放新对象,对对象中的子对象进行递归拷贝,拷贝前后两个对象互不影响
//赋值
var obj = {}
var person = {
name: '张三',
hobby:['学习','看电影','跑步']
}
var person1 = person
person1.name = '李四'
person1.hobby[0] = '玩游戏'
console.log(person) //{name: '李四',hobby:['玩游戏','看电影','跑步']}
console.log(person1) //{name: '李四',hobby:['玩游戏','看电影','跑步']}
//浅拷贝
function shallowCopy(obj){
var target = {}
for(var i in obj){
if(obj.hasOwnProperty(i)){
target[i]=obj[i]
}
}
return target
}
var person = {
name: '张三',
hobby:['学习','看电影','跑步']
}
var person2 = shallowCopy(person)
person2.name = '李四'
person2.hobby[0]='玩游戏'
console.log(person) //{name: '张三',hobby:['玩游戏','看电影','跑步']}
console.log(person2) //{name: '李四',hobby:['玩游戏','看电影','跑步']}
//☆☆☆深拷贝
//函数一
function deepClone(obj){
var cloneObj = obj.push ? [] : {};
if(obj === null) return obj
if(obj instanceof Date) return obj
if(obj instanceof RegExp) return obj
if (typeof obj != 'object') {
return obj
}
for(var i in obj){
if(obj.hasOwnProperty(i)){
cloneObj[i] = deepClone(obj[i])
}
}
return cloneObj
}
//函数二(简单)
function deepClone(obj) {
let newObj = obj.push ? [] : {};
for (var i in obj) {
if (typeof obj[i] === 'object') {
newObj[i] = deepClone(obj[i]);
} else {
newObj[i] = obj[i];
}
}
return newObj
}
var person = {
name: '张三',
hobby:['学习','看电影','跑步']
}
var person3 = deepClone(person)
person3.name = '李四'
person3.hobby[0] = '玩游戏'
console.log(person) //{name: '张三',hobby:['学习','看电影','跑步']}
console.log(person3) //{name: '李四',hobby:['玩游戏','看电影','跑步']}
/*浅拷贝的实现方式:
Object.assign()
...args
concat
深拷贝实现方式
$.extend
自定义函数deepClone
12.防抖函数
当持续触发事件 一定时间内没有再触发事件,事件处理函数才会执行一次,如果设定的事件来到之前又一次触发了事件,就重新开始延时
闭包:函数里面return出函数
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>防抖函数</title>
</head>
<body>
<input type="text" id="input">
<script>
var input = document.querySelector('#input')
//防抖函数的简单实现
function debounce(callback, delay) {
let timer
return function (value) {
clearTimeout(timer)
timer = setTimeout(function () {
callback(value)
}, delay)
};
}
function callback(value) {
console.log(value);
}
var func = debounce(callback,1000)
input.addEventListener('keyup', function (e) {
func(e.target.value)
})
//防抖函数运用apply
function debounce(callback, delay) {
let timer
return function () {
clearTimeout(timer)
timer = setTimeout(() => {
callback.apply(this, arguments)
}, delay)
};
}
input.addEventListener('keyup', debounce(function (e) {
console.log(e.target.value);
}, 1000)
)
</script>
</body>
</html>
13.节流函数
当持续触发事件的时候,保证一段时间内 只调用一次处理函数
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>节流函数</title>
</head>
<body>
<button id="button">123</button>
<script>
var btn = document.querySelector('#button');
//节流函数的简单实现
function throttle(callback, delay) {
let timer
return function () {
if (!timer) {
timer = setTimeout(function () {
callback.apply(this, arguments)
timer = null
}, delay)
}
}
}
//节流函数第二种编写方法
function throttle(callback, delay) {
let flag = true
return function () {
if (!flag) {
return
}
flag = false
setTimeout(() => {
callback.apply(this, arguments)
flag = true
}, delay)
}
}
btn.addEventListener('click', throttle(function () {
console.log(123)
}, 2000))
</script>
</body>
</html>
14.图片懒加载以及对图片懒加载优化
优势:增强用户体验,优化代码,减少http请求,减少服务器端压
原理:给img的src设置为空,同时给img标签设置一个特殊属性,例如:data-src(自定义设置)用于存放图片的真是预览地址;如果图片未进入可视区域,直接不展示图片,当图片进入可视区域,将data-src的值赋给src
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>图片懒加载</title>
<style>
img {
display: block;
height: 300px;
}
</style>
</head>
<body>
<div>
<img src="" data-src="img/f1.png" alt="" />
<img src="" data-src="img/f2.png" alt="" />
<img src="" data-src="img/f3.png" alt="" />
<img src="" data-src="img/f4.png" alt="" />
<img src="" data-src="img/f5.png" alt="" />
<img src="" data-src="img/f6.png" alt="" />
<img src="" data-src="img/f7.png" alt="" />
<img src="" data-src="img/f8.png" alt="" />
</div>
<script>
var imgs = document.querySelectorAll('img');
//图片懒加载实现
function lazyload() {
var scrollTop = document.documentElement.scrollTop
var viewportHeight = window.innerHeight || document.documentElement.clientHeight
for (let i = 0; i < imgs.length; i++) {
var height = scrollTop + viewportHeight - imgs[i].offsetTop
if (height > 0) {
imgs[i].src = imgs[i].getAttribute('data-src')
}
}
}
setInterval(lazyload, 1000);
//图片懒加载优化实现
var num = imgs.length
var n = 0
var isLoadImg = false //是否加载页面中的图片完成
function lazyload() {
isLoadImg = n >= num
var scrollTop = document.documentElement.scrollTop
var viewportHeight = window.innerHeight || document.documentElement.clientHeight
for (let i = n; i < num; i++) {
var height = scrollTop + viewportHeight - imgs[i].offsetTop
if (height > 0) {
imgs[i].src = imgs[i].getAttribute('data-src')
n++
}
}
}
function debounce(callback, delay) {
let timer
return function () {
clearTimeout(timer)
timer = setTimeout(() => {
callback.apply(this, arguments)
}, delay)
};
}
function throttle(callback, delay, isLoading) {
if (isLoadImg) {
return
}
let flag = false
return function () {
if (flag) {
return
}
flag = true
setTimeout(() => {
callback.apply(this, arguments)
flag = false
}, delay)
}
}
window.addEventListener('load',throttle(lazyload,100,isLoadImg))
window.addEventListener('scroll', throttle(lazyload, 100, isLoadImg))
window.addEventListener('resize',debounce(lazyload, 100))
</script>
</body>
</html>
15.手写Array.prototype.map方法
var arr = [1, 2, 3]
var array = arr.map((item, index) => {
return item * 2
})
console.log(array) //[2,4,6]
function map(arr, mapCallback) {
if (!Array.isArray(arr) || !arr.length || typeof mapCallback !== 'function') {
return []
} else {
let result = []
for (let i = 0; i < arr.length; i++) {
result.push(mapCallback(arr[i]))
}
return result
}
}
let res = map(arr, (item) => {
return item * 2
})
console.log(res); //[2,4,6]
16.策略模式以及表单验证
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>策略模式以及表单验证</title>
</head>
<body>
<form id="form" action="xxxx.html" method="post">
用户名:<input type="text" name="username">
密码:<input type="password" name="password">
手机号:<input type="text" name="usermobile">
<button>提交</button>
</form>
<script>
let form = document.querySelector('#form')
var 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 Validator = function () {
this.cache = []
}
Validator.prototype.add = function (dom, rule, errorMsg) {
var arr = rule.split(':')
this.cache.push(function () {
var strategy = arr.shift()
arr.unshift(dom.value)
arr.push(errorMsg)
return stra tegies[strategy](...arr)
})
}
Validator.prototype.start = function () {
for (let i = 0, vaFunc; vaFunc = this.cache[i]; i++) {
var msg = vaFunc()
if (msg) {
return msg
}
}
}
var validataFun = function () {
var validator = new Validator()
validator.add(form.username, 'isNonEmpty', '用户名不能为空')
validator.add(form.password, 'minLength:6', '密码长度不能小于6位')
validator.add(form.usermobile, 'isMobile', '手机号格式不正确')
var errorMsg = validator.start()
return errorMsg
}
form.onsubmit = function () {
let msg = validataFun()
if (msg) {
alert(msg)
return false
}
}
</script>
</body>
</html>
17.订阅发布模式
var Event = (function () {
var list = [],
listen,
trigger,
remove;
// 订阅
listen = function (key, fn) {
if (!this.list[key]) {
this.list[key] = []
}
this.list[key].push(fn)
}
// 发布
trigger = function () {
var key = Array.prototype.shift.call(arguments)
var fns = this.list[key]
if (!fns || fns.length === 0) {
return
}
for (let i = 0, fn; fn = fns[i]; i++) {
fn.apply(this, arguments) //也可以用fn(...arguments)
}
}
// 取消订阅
remove = function (key, fn) {
var fns = this.list[key]
if (!fns) {
return false
}
if (!fn) {
fn && (fns.length = 0)
} else {
for (let i = fns.length - 1; i >= 0; i--) {
if (String(fn) == String(fns[i])) {
fns.splice(i, 1)
}
}
}
}
return {
list,
listen,
trigger,
remove
}
})()
Event.listen('red', function (size) {
console.log('尺码是' + size)
})
Event.listen('black', function (size) {
console.log('尺码是' + size)
})
Event.trigger('red', 42) //尺码是42
Event.trigger('black', 43) //尺码是43
Event.remove('red', function (size) {
console.log('尺码是' + size)
})
Event.trigger('red', 43) //因为标识符为red的订阅被移除了,这里不会输出任何东西
18.高阶函数
把一个函数如果接受另外一个函数作为参数,或者该函数会返回另外一个函数作为返回值的函数