js运行机制,设计模式及语法

JavaScript作为一门运行在浏览器上的弱类型,解释型语言。

本文将先从作用域和事件循环机制来讨论js的运行机制,

再讨论三种设计模式如何用js实现及相关应用,

接着是关于js的一些语法,主要分为逻辑,数据类型和函数,再深入讨论ES6的语法,

最后是一些模块封装和常用模块了解,导入等

作用域

概念

一般值变量作用域

全局作用域

什么时候创建和销毁?页面打开,关闭

window全局对象打开页面即创建

全局作用域声明的变量和函数会作为window的属性和方法保存

函数作用域

创建和销毁?调用函数,执行函数完毕

在函数作用域中访问变量和函数,会先在自身作用域寻找

若没有找到,则会到上一级,直到全局作用域

预编译

全局代码执行前期(预编译),会创建执行期上下文的对象GO(全局作用域)

函数代码执行(函数调用)的前期(预编译),会创建执行期上下文的内部对象A0(函数作用域)

全局作用域的预编译

  1. 创建GO对象
  2. 找变量声明 作为GO对象的属性名,值是undefined
  3. 函数声明function a( ){ } 会覆盖变量声明

函数作用域的预编译(常问)

  1. 创建AO对象
  2. 找形参和变量的声明 作为AO对象的属性名,值是undefined
  3. 实参和形参相统一
  4. 函数声明function a( ){ } 会覆盖变量声明

注意: var b = function ( ){ } 函数表达式,不是函数声明,是变量b声明

作用域链

被保存在隐式的属性中[[scope]],这个属性我们用户访问不到,js引擎访问

AO和GO的集合

			<script>
        // 全局域编译GO
        var gl
        function a() {
            function b() {
                var bbb = 1
                var aaa = 2
            }
            b() 
            // b函数预编译AO,作用域链:
            // 0:bbb,aaa (b函数的AO)
            // 1: function b, aa(a函数的AO)
            // 2:gl, function a (GO)
            var aa = 0
        }
        a() // a函数预编译AO
    </script>

函数执行完销毁与作用域链

		<script>
        function a(){
            var aa = 123
            return function b(){	//b函数作用链中,可以拿到aa
                var bb = 234
                console.log(aa)
            }
        }
        var res = a()   
        // b函数
        // 虽然a执行完,销毁了,但b函数被保存在res里了
        // a的销毁,只是a的作用域链销毁,并不影响 b的作用域链,b依然可以拿到aa
        res()   // 123
    </script>
闭包

a函数内 return b函数,a函数内部的变量可以被外部的b函数使用,不会随着a函数执行完毕而被销毁

b函数在出生的时候(第一次调用),能够访问到a函数的变量,存在作用链中,之后即使a函数执行完被销毁,b函数的作用链不会改变,一样可以访问到a函数的变量

应用:

debounce和throttle防抖和节流:定时器的变量都用到闭包

单例模式:res = fn.apply(this,arguments),保留上次的执行结果

防抖节流

有个封装好的工具,lodash,在html页面引入js

_.debounce(函数,间隔时间)

防抖函数

定时器

触发事件的同时,开始定时器

定时期间,再次触发事件,事件不执行,定时器重新开始定时

过了定时期间,再次触发事件,事件执行,定时器再次开始定时

​ 应用场景:动画渲染防止频繁操作,输入框防止频繁提交,解决异步操作的bug()

		<input type="text" id="input">
    <script>
        // 1秒只能用enter键提交一次
        function debounce(callback,delay){
            let timer       
            // timer变量,函数执行期间要一直存在内存当中,如果没有闭包会造成内存泄漏
            // 解决方法:闭包,函数执行结束即顺带把变量销毁了
            return function(value){
                clearTimeout(timer)     //先clear,之前触发的定时器清除掉,timer变为undefined
                timer = setTimeout(function(){      // 再set,重新开始定时
                    callback(value)
                },delay)
            }
        }
        // 功能函数callback
        function func(value){
            console.log(value)
        }
				
      	// 交互
        var input = document.getElementById('input')
        var debounceFn = debounce(func,1000)    //返回function(arg){}
        // 监听input的keyup事件,enter后将input标签里的内容打印到consloe控制台上
        input.addEventListener('keyup', function(e){
            debounceFn(e.target.value)      // 监听input获取值e.target.value
        })
			</script>
节流函数

一段时间内,只做执行一次事件

应用场景:表单提交,鼠标点击多次,只生效一次

		<button id="button">点击</button>
    <script>
        function throttle(callback,delay){
            let timer
            return function(){
                timer = setTimeout(function(){
                    callback()
                    timer = null    
                  // 完成功能后,后面的操作都不管,等到该定时器销毁,新操作才有效
                },delay)
            }
        }
        // 功能函数callback
        function func(){
            console.log(Math.random())      // 输出0-1之间的数字
        }
      	// 交互
        // 监听点击事件onclick = 函数(简写)相对于用addEventListener('事件',函数)
        document.getElementById('button').onclick = throttle(handle,2000)
懒加载
A lazy image

<------ 滚动到特定位置的时候 ------>

A lazy image

结合防抖函数和节流函数,根据可视区域,加载图片

		<img src="" data-src="270*270.jpg">
    <script>
        var num = document.getElementsByTagName('img').length
        var img = document.getElementsByTagName('img')
        var n = 0      // 存储图片加载到的位置,避免每次都从第一张图片遍历
        var isLoadImg = false   //图片是否都加载完成
        var _clientHeight = document.documentElement.clientHeight   //可见区域的高度,有完整兼容写法,见瀑布流功能
        var _srollTop = document.documentElement.scrollTop || document.body.scrollTop
        // 滚动条距离顶部的高度,有兼容写法,见瀑布流功能

        // 监听窗口变化(防抖函数)的功能函数:计算可见区域
        function computedClientHeight(){
            _clientHeight = document.documentElement.clientHeight
        }
        
        // 滚动屏幕(节流函数)的功能函数:根据可视区域,加载图片
        function lazyload(){
            isLoadImg = n >= num		// isLoadImg是 true 还是 false
            _srollTop = document.documentElement.scrollTop || document.body.scrollTop

            // 从第n张开始遍历,加载
            for (var i = n; i < num; i++){
                if (img[i].offsetTop < _clientHeight + _srollTop){
                    // 可见区域+滚动条的高度 大于 图片高度,要将图片显示出来
                    if (img[i].getAttribute('src') == ''){
                        // 接着如果src属性为空,要将图片显示出来,需要给属性值
                        img[i].src = img[i].getAttribute('data-src')
                    }
                    n = i + 1   // 不用管判断属性值是否为空,只要图片显示出来,就加1
                }
            }
        }
        
        // 节流函数
        function throttle(callback,delay,flag){
            let timeout
            return function(){
                if (flag) {     // 图片如果加载完成,直接return退出
                    return
                }
                if (!timeout){
                    timeout = setTimeout(function(){
                        callback()
                        timeout = null    
                    // 完成功能前,别的操作都不管,等到该定时器销毁,新操作才有效
                    },delay)
                }
                
            }
        }

        // 防抖函数
        function debounce(callback,delay){
            let timer
            return function(){
                clearTimeout(timer)
                timer = setTimeout(function(){
                    callback()
                },delay)
            }
        }
				
				// 交互
        // 先初始化懒加载
        lazyload()
        
        // 加载图片需要一定的时间,滚动屏幕限制,节流函数
        window.addEventListener('scroll',throttle(lazyload,100,isLoadImg))
        
        // 监听可视区域高度变化,防止频繁滚动屏幕多次计算,防抖函数
        window.addEventListener('resize',debounce(computedClientHeigh,800))

单例模式

res一直保存在内存中,用到闭包

判断是否已经存在res,单例模式

				// 单例模式(闭包)
        var getSingle = function(fn){
            var res;
            // res这个变量会一直保存在内存中,第一次是undefined,之后就都有值了
            // (Boolean(undefined)-->false)
            return function(){
                return res || (res = fn.apply(this,arguments))
                // 第一次前面不成立,走后面
                // 之后res都有值了,就直接用res即可,不用走后面了
            }
        }
内存泄漏

函数内的变量,不会随着函数执行完毕而销毁

有哪些?

  1. 闭包
  2. 意外的全局变量(函数内没有声明的变量,默认为全局变量)
  3. 被遗忘的定时器
  4. 脱离dom的引用(获取了dom的引用,后面元素被删除了,但是一直保留着对元素的引用)

js的运行机制

单线程:同一时间做一件事情

因为js离不开和用户的操作,不然会造成与用户交互发生混乱

arguments:类数组对象

箭头函数没有arguments对象

经常用于替代函数的形参

function get( ){

​ console.log(arguments)

}

get(1,2,3)

将类数组对象转化为数组对象

  1. 展开运算符…(ES6)

  2. Array.prototype.slice.call(agruments)

eventLoop

事件循环机制

js内存模型

call stack 调用栈:执行主进程任务(基本数据类型)

函数调用时,会被压入调用栈中,被压入的函数叫帧,当函数执行完毕后会从调用栈中弹出

heap 堆:存储非结构化数据,对象(函数,数组,对象)(引用数据类型)

消息队列(宏任务):异步操作(如fetch setTimeout等)函数,压入到调用栈中的时候里面的消息会进入到消息队列

消息队列中的,会等到调用栈 清空 才会执行

微任务队列:异步操作(如promise,async,await),加入到微任务中

微任务中的,会在调用栈清空的时候 立即执行(先于消息队列宏任务)

promise前面的都会立即执行,resolve里的值会传给then,then后才会进入微任务队列,

task queue 任务队列:存放异步任务与定时任务

js代码执行机制
同步任务与异步任务
  1. 同步任务在主线程的栈中按顺序执行
  2. 异步任务有了运行结果,就会在任务队列中放置一个事件
  3. 栈中所有同步任务执行完毕,系统读取任务队列,选出需要首先执行的任务(顺序由浏览器决定,并不按序)
宏任务与微任务
  1. MacroTask(宏观任务) setTimeout, setInterval, , requestAnimationFrame, I/O

  2. MicroTask(微观任务) process.nextTick, Promise, Object.observe, MutationObserver

  3. **先同步任务,再取出第一个宏任务执行,所有的相关微任务总会在下一个宏任务之前全部 执行完毕 **

    如果遇见 就 先微后宏

promise内的直接执行,then后面的为微任务

设计模式

写js的一些思想

单例模式

只有一个实例,可以全局访问

应用场景:一个全局使用的类,频繁的创建和销毁,1. 首页页面的缓存 2.弹窗

如何实现:判断系统中是否已经有这个实例,如果有直接返回,没有则创建

思想:单一职责,耦合度更低

实现登录功能——弹窗:

ES5 (闭包)

res一直保存在内存中,用到闭包

判断是否已经存在res,单例模式

		<button id="loginBtn">登录</button>
    <script>
        // 单例模式(闭包)
        var getSingle = function(fn){
            var res;
            // res这个变量会一直保存在内存中,第一次是undefined,之后就都有值了
            // (Boolean(undefined)-->false)
            return function(){
                return res || (res = fn.apply(this,arguments))
                // 第一次前面不成立,走后面
                // 之后res都有值了,就直接用res即可,不用走后面了
            }
        }
        
        // 弹出登录窗口(功能)
        var createLogin = function(a,b,c){
            var div = document.createElement('div')
            div.innerHTML = '登录窗口'
            div.style.display = 'none'
            document.body.appendChild(div)
            return div
        }

        // 交互(全局作用域),点击事件-调用函数
        var create = getSingle(createLogin)
        document.getElementById('loginBtn').onclick = function(){
            var loginLay = create(1,2,3)
            loginLay.style.display = 'block'
        }
    </script>
ES6 (类静态方法)

类:ES5 构造函数的语法糖

某个类只能new出一个实例,功能写在类里

类的静态方法,类能调用

				class Foo{
            constructor(name){
                this.name = name
            }
            
            // 类的静态方法,类可以调用,实例不能调用
            // 如果没有实例创建实例,有实例直接返回实例
            static getInstance(name){
                if(!this.instance){
                    this.instance = new Foo(name)
                }
                return this.instance
            }
        }

        let foo = Foo.getInstance('小明')
策略模式

算法封装

将算法的实现(策略)和算法的使用(区分) 分离 开来

				// 提供三种策略供选择使用
        // 对象的属性值为函数
        var strategies = {
            'S': function(salary){
                return salary * 4
            },
            'A': function(salary){
                return salary * 4
            },
            'B': function(salary){
                return salary * 4
            }
        }
        
        // 选一种策略使用函数
        var getBouns = function (level,salary){
            return strategies[level](salary)
        }

        // 调用函数
        var bouns = getBouns('S',10000)
        console.log(bouns)

应用场景:表单验证封装

  1. 构造验证策略
  2. 将策略的使用封装成构造函数(类)
  3. 使用类完成验证,实现交互
		<form action="" method="post" id="registerForm">
        请输入用户名:
        <input type="text" name="username">
        请输入密码:
        <input type="password" name="password">
        请输入手机号码:
        <input type="text" name="phonenumber">
        <button>提交</button>
    </form>

    <script>
        // 1. 验证策略
        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
                }
            },
        }

        // 2. 策略的使用封装成构造函数(类),add,start方法
        var Validator = function(){
            this.cache = [] // 保存验证规则的数据
        }
        // 将 调用不同策略的函数 加入到cache中
        Validator.prototype.add = function (dom,rule,errorMsg){
            var ruleArr = rule.split(':')   // rule = strategy:中间的参数(length)
            this.cache.push(function(){     // 往cache数组里添加函数
                var strategy = arr.shift()  
                // 将ruleArr里的第一个元素移出数组ruleArr,并返回给strategy
                ruleArr.unshift(dom.value)  // 将dom.value加到ruleArr中,放在第一个
                ruleArr.push(errorMsg)      
              // ruleArr = [dom.value,参数比如length,errorMsg]
                // 上面的各种操作都是为了将传入的参数改为如下,从而可以去调用不同策略
                return strategies[strategy](...ruleArr)     // 
            })

        }
        // 遍历cache,调用函数
        Validator.prototype.start = function(){
            for (var i = 0, vaFunc; vaFunc = this.cache[i++];){
                var msg = vaFunc()
                if (msg) {
                    return msg  //只要有错,立马返回错误信息,并退出循环,不再继续验证
                }
            }
        }

				// 3. 调用类(用函数包住),交互
        // 验证功能
        var registerForm = document.getElementById('registerForm')

        // 用函数包住,函数执行完后会,自动销毁内部的变量
        var validateFun = function(){
            var validator = new Validator()
            validator.add(registerForm.username, 'isNonEmpty','用户名不能为空')
            validator.add(registerForm.password, 'minLength:6','密码长度不能小于6位')
            validator.add(registerForm.phonenumber,'isMoblie','手机格式不正确')
            var errorMsg = validator.start()
            return errorMsg
        }
        
        // 交互,监听提交事件,并将结果展示到页面上
        registerForm.onsubmit = function(){
            var errorMsg = validateFun()
            if (errorMsg){
                alert(errorMsg)
                return false
            }
        }

    </script>
发布订阅模式

低耦合,发布key和订阅fn分开

登录成功,主组件将信息发布出来,不同子模块订阅展示出来,跨组件传值

主组件发布trigger,其他组件子组件,孙组件等listen,获取值

pubsub.js发布订阅模式实现如下:

				// 发布订阅模式封装到utils,export default Event供使用

        var Event = (function(){
            var list = {},
                // list = {
                //     key0:[fn0,fn1],
                //     key1:[fn0,fn1]
                // }
                listen,
                trigger,
                remove;
            // 往list加fn
            listen = function(key,fn){
                if(!list[key]){
                    list[key] = []
                }
                list[key].push(fn)
            }
            // 取出fn并执行
            trigger = function(){
                var key = Array.prototype.shift.call(arguments)
                // key:数组
                var fns = list[key]
                if (!fns || fns.length == 0){
                    return
                }
                for (var i = 0,fn; fn = fns[i++];){
                    fn(...arguments)
                    // fn.apply(arguments)
                }
            }
            // 移除fn
            remove = function(key,fn){
                var fns = list[key]
                if (!fns){
                    // 没有这个fns数组来查fn
                    return false
                }
                if (!fn){
                    // 没有传入fn,则将fns.length赋值为0
                    fn && (fns.length = 0) 
                    // 短路表达式:
                    // &&返回第一个假值,或最后一个真值
                    // ||返回第一个真值,或最后一个假值
                    // 0、""、null、false、undefined、NaN都会判定为false

                }else{
                    // 从后往前查找是否有fn,倒序遍历
                    for (var i = fns.length -1; i>=0; i--){
                        var _fn = fns[i]
                        if (_fn == fn){
                            fns.splice(i,1)
                            // spliec在index为i的地方,移出1个元素
                        }
                    }
                }
            }
            return {
                listen,
                trigger,
                remove
            }
        })()

        export default Event

拓展:

可以按照pubsub-js包

逻辑

if

if ( 判断false还是true ) { }

重点:0、""、null、false、undefined、NaN都会判定为false

三元表达式
Array.isArray(cur) ? flatten(cur) : cur
if (Array.isArray(cur)){
  	return flatten(cur)
}else{
  	return cur
}
短路表达式

(函数里不用写return)

&&返回第一个假值,或最后一个真值

||返回第一个真值,或最后一个假值

提前退出(解构)

判断一个对象里的key是否全,解构{ }

const printDetails = ({type,name,gender} = {}) =>{
  	if(!type) return 'no type'
  	if(!name) return 'no name'
  	if(!gender) return 'no gender'
  	return '${name} is a ${gender} ${type}'
}
console.log(printDetails({type:'dog',name:'wang',gender:'female'}))

数据类型

基本数据类型:number string boolean undefined null 存储在栈里

引用数据类型:obj 存储在堆里

栈指向堆

字符串

.indexof(值) 字符串里如果没有这个值,返回-1(===)

.split(’’).reverse().join(’’)

对象

对象字面量代替switch-case

// 对象字面量
const cases = {
  	one:['a'],
  	two:['b']
}
function printCases(num){
  	return cases[num] || []
}
console.log(printCases(null))		// []
console.log(printCases('one'))	// ['a']

key会自动转化为字符串,隐式调用toString

var obj1 ={
  	name:'zs'
}
var obj2 ={
  	name:'ls'
}
var obj3 ={
  	[obj1]:'11',
  	[obj2]:'22'
}
console.log(obj3)		//{[object Object]:'22'}

用Map代替{ },set和get,不会隐形toString

// 对象字面量
const cases = new Map().set('one',['a']).set('two',['b'])
// {one:['a'],two:['b']}
function printCases(num){
  	return cases.get(num) || []
}
console.log(printCases(null))		// []
console.log(printCases('one'))	// ['a']
array
方法
加入,移出元素

array.shift(元素or数组)移出,并返回,没有传入的话,默认第一个元素

.splice(index,个数)从index移出n个元素

ps: Array.prototype.slice.call(div) 不会修改数组,而是返回一个子数组 (ie8以下不能用)

.unshift( )从前加入元素,返回length

.push(元素,元素)从后加入元素,改变原数组

.concat(元素,元素)将参数合并入数组,不会改变原数组,返回一个新数组,可接收

push和concat如果加入的是数组,push会直接把数组加入,而contat会把数组解析为元素后加入

// 合并两个数组
let arr1 = [1,2,3]
let arr2 = [4,5,6]

// 1.apply
Array.prototype.push.apply(arr1,arr2)
console.log(arr1)		// [1,2,3,4,5,6]

// 2.扩展运算符...
判断

Array.isArray(参数)判断参数是否为数组

.includes(参数)判断参数是否在数组中

.length

复杂应用

(手写原生特性)

.flat(几层) 扁平化处理

.every(函数)函数的参数item,函数里的每个item都满足,才会返回true

// 判断是否全为红色
const array =[
  	{name:'a', color:'red'},
  	{name:'b', color:'yellow'},
]
function isAllred(){
  	const res = array.every(item => item.color == 'red')
    console.log(res)
}
isAllred()	// false

.some(函数)函数的参数item,函数里只要有一个item都满足,就会返回true

// 判断是否有为红色
const array =[
  	{name:'a', color:'red'},
  	{name:'b', color:'yellow'},
]
function isAllred(){
  	const res = array.some(item => item.color == 'red')
    console.log(res)
}
isAllred()	// true

.map(函数) 处理每个元素,返回新的数组,函数的参数item,index

.fliter(函数) 处理每个元素,返回符合条件的新的数组,函数的参数item

.filter( (item)=> ( 条件 ) )ps:直接(条件)箭头函数语法,不用写return

.filter((item)=>( (item.id-0) === id ) )
// item.id-0转化为num类型,传入的id参数直接是num类型即可
// goods数据数组的filter方法:传入函数,该函数的参数为item单条数据,返回符合条件的item集合数组

.reduce(函数,初始值) 回调函数的参数:pre,cur,index,array(调用改方法的数组)

可用于:

  1. 数组扁平化(concat)

  2. 计算数组内值的合,乘积

  3. 计算元素出现的次数

let person = ['a','b','c','a','d']
let calNum = person.reduce((pre,cur) => {
  	// pre:{'a':2,'b':1}
  	// cur:'a'
  	if (cur in pre){
      	pre[cur]++
    }else{
      	pre[cur] = 1
    }
  	return pre
},{})
  1. 数组去重

    如果要元素去重,先扁平化,再如下去重

var array = [2,3,3,2,4,5,6]
let unrepeated = array.reduce((pre,cur) =>{
  	if (pre.includes(cur)){
      	return pre
    }else{
      	return pre.push(cur)
      	// 扁平化后push和concat均可
    }
},[])

对象{ }key是否在里面,直接in

数组[ ]元素是否在里面,array.includes(元素)

拓展:

arguments类数组,箭头函数,其他函数有,用上面方法时,要Array.prototype.shift.call(arguments)用call

区别于调用函数传入类数组arguments参数用apply,也可以用…扩展符

数组扁平化处理

将多维数组变为一维数组

  1. array.flat(几层) 还可传入Infinity

  2. 正则去除

    var str = JSON.stringfy(array).replace(/\[|\]/g,'').split(',');
    var arr = JSON.parse( '['+ str + ']' )
    
  3. for+递归

    var arr = [];
    const flatten = array =>{
      	for (let i = 0; i<array.length; i++){
          	// 判断array[i]是否为数组,是的话,递归
          	if (Array.isArray(array[i])){
              	fn(array[i]);
            }else{
              	arr.push(array[i])
            }
        }
    }
    flatten(array);
    
  4. reduce+递归

    const flatten = array =>{
      	return array.reduce(
          	// reduce可以处理数组上次回调返回的值pre(或初始值[])和当前值cur
          	(pre,cur)=>{
          			return pre.concat(Array.isArray(cur) ? flatten(cur) : cur);
              	// concat把参数合并进数组,将非数组的cur与pre合并后返回
        		},[])		// []初始值
    }
    var arr = flatten(array);
    
手写原生的特性
  1. 特性的功能
Array.prototype.map

1)了解功能:map方法可以传入一个函数,返回新的数组

		<script>
        var arr = [1,2,3]
        // map可以传入一个函数,返回新的数组
        var array = arr.map((item,index) => {
            return item * 2
        })
        console.log(array)
    </script>

2)开始写map函数:一,检查传入参数是否正确 二,将新的item用for循环push到新的数组中

		<script>
        var arr = [1,2,3]
        // map可以传入一个函数,返回新的数组
        var array = arr.map((item,index) => {
            return item * 2
        })
        console.log(array)

        // 写map方法,要传入arr
        function map(arr,callback){
            // 检查参数是否正确,boolean(0)-->false
            // 不是数组,数组为空,不是函数
            if (!Array.isArray(arr) || !arr.length || typeof callback !== 'function'){
                return []
            }else{
                // 参数正确进行如下操作
                let res = []
                for (let i=0, len=arr.length; i<len; i++){
                    res.push(callback(arr[i],i,arr))   // 要传入arr
                }
                return res
            }
        }

        map(arr,(item)=>{
            console.log(item)
        })
    </script>
Array.flat(n)

数组扁平化

Array.isArray

判断是否为数组

let arr = [1,2,3]
function isArray(array){
  	return Object.prototype.toString.call(array) === '[object Array]'
  	// 如果判断是否为对象[object Object]
  	// [object String]
  	// [object Null]
}
赋值与拷贝
  1. 赋值:将栈中的内存地址给新变量,同一个地址(栈)
// 赋值(栈中相同地址)
        var person = {
            name: "a",
            hobby: ["study", "play", ["book", "stamp"]]
        };
        var person1 = person;
        person1.name = "b"
        person1.hobby[0] = "learn"

        console.log(person)
        console.log(person1)    // person和person1一样,name和hobby都被修改了
  1. 浅拷贝:创建一个新对象(栈)

属性是基本数据类型,拷贝的是值(hasOwnProperty)

属性是引用数据类型,拷贝的是内存地址

				var person = {
            name: "a",
            hobby: ["study", "play", ["book", "stamp"]]
        };

        // 浅拷贝(栈中地址不同,堆中地址相同)
        function shallowCopy(obj){
            var target = {}
            for (var i in obj){
                if (obj.hasOwnProperty(i)){
                    target[i] = obj[i]
                }
            }
            return target
        }

        var person1 = shallowCopy(person)
        person1.name = "b"
        person1.hobby[0] = "learn"

        console.log(person)
        console.log(person1)
        // person 和 person1 不一样,一个的name是a,一个是b,但是hobby[0]都为learn

浅拷贝:

Object.assign( )

concat

loash:clone

  1. 深拷贝:栈+堆

修改新对象不会影响原对象

				var person = {
            name: "a",
            hobby: ["study", "play", ["book", "stamp"]]
        };

        // 深拷贝 栈和堆地址都不同,递归
        function deepClone(obj){
            var cloneObj = new obj.constructor()
            // var cloneObj = {}

            if (obj === null) return obj
            if (obj instanceof Date) return new Date(obj)
            if (obj instanceof RegExp) return new RegExp(obj)
            if (typeof obj !== 'object') return obj

            for (var i in obj) {
                if (obj.hasOwnProperty){
                    cloneObj[i] = deepClone(obj[i])
                }
            }
            return cloneObj
        }

        var person1 = deepClone(person)

        person1.name = 'b'
        person1.hobby[0] = 'learn'

        console.log(person)
        console.log(person1)
        // person 和 person1 name和hobby都不同

拓展:如果对象里没有date和function,可以考虑用json深拷贝

var person1 = JSON.parse( JSON.stringify(person) )

date会被为{},function会直接没了

深拷贝:

$.extend

deepClone

函数

函数即对象,有方法

.call(对象,参数)

.apply(对象,参数类数组) 类数组也可用es6解构…

.bind(obj)

都可以改变this指向:用别人的东西,代码复用

bind不会立即执行,call和apply会

let bind = child.showName.bind(obj)

bind( )

应用场景:伪数组转化为数组

伪数组:arguments,获取的dom元素产生的HTML集合getElementsBy,

  1. slice+call (ie8以上)
var div = document.getElementsByTagName('div')
var arr = Array.prototype.slice.call(div)
  1. for循环(兼容)
function listToArray(likeArray){
  	var arr = []
    try{
      	arr = Array.prototype.slice.call(likeArray)
    }catch(e){
      	for (var i = 0; i < likeArray.length; i++){
          	arr[arr.length] = likeArray[i]
          	// 相当于arr.push(likeArray[i])
        }
    }
}
this
  1. 在函数中直接使用

    function get(content){

    ​ console.log(content)

    }

    get.call(window,“你好”) 的语法糖:get(“你好”)

  2. 函数作为对象的方法被调用

    var person = {

    ​ name : “张三”,

    ​ fun: function (time) {

    ​ console.log(" t h i s . n a m e 在 跑 步 最 多 {this.name} 在跑步 最多 this.name{time}min就不行了")

    ​ }

    }

    person.run.call (person,30) 的语法糖:person.run(30)

    输出:张三在跑步 最多30min就不行了

语法糖倒推call,找出this具体指代哪个

var name = 222
var a = {
  name: 111,
  say: function () {
    console.log(this.name)
  }
}

var fun = a.say
fun()   // fun.call(window) 222
a.say() // a.say(a) 111

var b = {
  name: 333,
  say: function (fun) {
    fun()
  }
}
b.say(a.say)    
// b.say(b,a.say) fun()没有this,所以b不用管
// 接下来执行内嵌的fun(),相当于fun(window)即a.say.call(window)
// 222

b.say = a.say   // 直接把b的say方法换为console.log(this.name)
b.say()     //b.say.call(b) 333
  1. 箭头函数的this

    上面的call来确定this的指向,执行时才确定this执行

    箭头函数是在定义函数时,this指向就确定了

    因为箭头函数没有自己的this,导致内部的this就是外层代码的this

    同时,因为没有this,箭头函数也不能用作构造函数

    				// 箭头函数的this
            var x = 11;
            var obj = {
                x: 22,
                say: () => {
                    console.log(this.x);
                }
            }
            obj.say();      //箭头函数没有this,用的外部的,所以还是window 11
    
            var ob = {
                birth: 1990,
                getAge: function () {
                    var b = this.birth;     
                    var fn = () => new Date().getFullYear() - this.birth;
                    return fn();
                }
            }
            var d = ob.getAge();    
            // ob.getAge.call(ob),var b = 1990, var fn 箭头函数this指向外部为obj对象
            console.log(d)
            //  fn() 2021-1990 = 31
    
高阶函数

将函数作为 参数或者返回值 的函数

构造函数

首字母大写

属性(this)和方法,可实例化

语法糖为es6的类

				// 构造函数
        // 属性
        function Person(name,sex){
            this.name = name
            this.sex = sex
        }
        // 方法
        Person.prototype.say = function(){
            console.log('自我介绍')
        }

        // 实例化
        let person1 = new Person('小明','男')
        console.log(person1.name)
        person1.say()
继承
原型链的继承

缺点:

  1. 继承后的类产生的实例,改变一个实例,会导致另一个实例也跟着改变
  2. 没有实现super功能(对父类进行传参)
function Parent(){
  	this.name = ['a']
}
Parent.prototype.getName = function(){
  	return this.name
}
// 原型链的继承
function Child(){
  
}
Child.prototype = new Parent()
Child.prototype.constructor = Child
const child1 = new Child()
const child2 = new Child()
child1.name[0] = 'b'
console.log(child1.name)
console.log(child2.name)
// 都输出的[‘b’]
构造函数的继承

在子类的构造函数中,执行父类的构造函数,并为其绑定子类的this(call)

解决:改变一个实例,不会导致另一个实例跟着改变

缺点:不能继承父类原型上的方法和属性

function Parent(name){
  	this.name = [name]
}
Parent.prototype.getName = function(){
  	return this.name
}
// 子类的构造函数的继承
function Child(){
  	Parent.call(this,'a')
}
// 不能继承父类原型上的方法和属性
// Child.prototype.constructor = Child

const child1 = new Child()
const child2 = new Child()
child1.name[0] = 'b'
console.log(child1.name)
console.log(child2.name)
// [‘b’] ['a']
console.log(child2.getName())
// 报错,不能继承父类原型上的方法和属性
组合式继承

把上面两个组合一下 构造函数的继承+原型链的继承

缺点:每次生成一个子类的实例,都要call和new,父类执行两次

function Parent(name){
  	this.name = [name]
}
Parent.prototype.getName = function(){
  	return this.name
}
// 子类的构造函数的继承
function Child(){
  	Parent.call(this,'a')
}
// 继承父类原型上的方法和属性(原型链的继承)
Child.prototype = new Parent()
Child.prototype.constructor = Child

const child1 = new Child()
const child2 = new Child()
child1.name[0] = 'b'
console.log(child1.name)
console.log(child2.name)
// [‘b’] ['a']
console.log(child2.getName())
// ['a']
寄生式继承

减去一次父类的执行

Child.prototype = new Parent() 改为Parent.prototype

缺点:子类对原型的操作会影响到父类的原型

Parent.prototype改为Object.create(Parent.prototype)

function Parent(name){
  	this.name = [name]
}
Parent.prototype.getName = function(){
  	return this.name
}
// 子类的构造函数的继承
function Child(){
  	Parent.call(this,'a')
}
// 继承父类原型上的方法和属性(原型链的继承)
Child.prototype = Object.create(Parent.prototype)
Child.prototype.constructor = Child

const child1 = new Child()
const child2 = new Child()
child1.name[0] = 'b'
console.log(child1.name)
console.log(child2.name)
// [‘b’] ['a']
console.log(child2.getName())
// ['a']

ES6语法

块级作用域

let(块作用域),const,var

箭头函数
数据结构

魔板字符串

symbol

对象

展开运算符…

对象解构,数组等

class类

es5构造函数的语法糖,面向对象的写法,继承更简单

(属性constructor+方法),继承(extends,super), 实例化(new)

constructor 实例化的时候就会调用,初始化

例子如下:

class Person {
    constructor(){
      this.name='建林'
      this.age=18
    }
    say() {
      console.log('say方法')
    }
}

class Teacher extends Person {
    constructor(){
        super();// 继承必须写super 他就是父类 上面的那个 constructor
       
        this.name='思聪'
    }
    eat(){
      console.log('eat')
    }
}

let t1=new Teacher()
console.log(t1)
静态方法(static)

只能被类本身调用,不能被实例调用

				class Foo{
            static classMethod(){
                return 'hello'
            }
        }
        console.log(Foo.classMethod())
Decorator

装饰器,注释或修改类和类方法@,放在类前面或类方法前面,普通函数不要使用

进入代码就会执行

//装饰类Foo
@frozen 
class Foo {
	
  //装饰method方法
  @configurable(false)
  method() {}
  
 //装饰yy方法
  @throttle(500)
  yy() {}
  
}
修饰类(@不带参数)

frozen是一个带targer参数的函数,相当于调用frozen,给类Foo加上一些代码

function frozen(target) {
  target.isfrozen = true;
}

@frozen 
class Foo {
  // ...
}

Foo.isfrozen // true
//为它加上了静态属性isfrozen。frozen函数的参数target是Foo类本身。
修饰类(@带参数)

嵌套函数,return函数

function frozen(isfrozen){
  return function(target){
    target.isfrozen = isfrozen;
  }
}

@frozen(true) 
class Foo {
  // ...
}

Foo.isfrozen // true

@frozen(false) 
class MyFoo {
  // ...
}

Foo.isfrozen // false
修饰类方法

函数参数三个:1.哪个类target 2. 要修饰的属性名name 3.属性的描述对象descriptor

// descriptor对象原来的值如下
// {
configurable:false,//能否使用delete、能否需改属性特性、或能否修改访问器属性、,
false为不可重新定义,默认值为true
enumerable:false,//对象属性是否可通过for-in循环,flase为不可循环,默认值为true
writable:false,//对象属性是否可修改,flase为不可修改,默认值为true
value:‘xiaoming’ //对象属性的默认值
// };

function frozen(target, name, descriptor){

  descriptor.writable = false;
  return descriptor;
}

class Person {
  @readonly
  abc() { console.log('我是person的abc函数') }
}
执行顺序

洋葱模型,由外到内进入,再由内到外执行

异步Promise

async,await

回调地狱

回调函数:在需要的时候就可以调用对应函数,在函数中调用函数

async,await(Generator)

Generator函数的语法糖

语法糖:功能不受影响,增加可读性

Generator函数(生成器):可以返回一系列值,yield

优化代码:将generator函数(yield)改为async函数(await)

  const fs = require('fs');

  const readFile = function (fileName) {
    return new Promise(function (resolve, reject) {
      fs.readFile(fileName, function(error, data) {
        if (error) return reject(error);
        resolve(data);
      });
    });
  };

  const gen = function* () {
    const f1 = yield readFile('/etc/fstab');
    const f2 = yield readFile('/etc/shells');
    console.log(f1.toString());
    console.log(f2.toString());
  };
  改造

  const asyncReadFile = async function () {
  const f1 = await readFile('/etc/fstab');
  const f2 = await readFile('/etc/shells');
  console.log(f1.toString());
  console.log(f2.toString());
};

ps:读文件返回promise对象这个函数多次使用,所以,再抽象出一个带参数的函数返回promise对象,调用该函数,传入文件名,即可读取对应的文件

promise-then

async函数返回promise对象,then方法添加回调函数

回调函数执行时,如果遇到await会先返回

等异步操作完成(then后的微任务),再接着执行函数体内后面的语句

概念
执行顺序

(池子)

  1. 同步异步,微任务宏任务(setTimeout),pending,resolve,reject

同步代码

resolve——then微任务的异步代码

宏任务的异步代码

数字打印的顺序
const first = () => (new Promise((resolve, reject) => {
    console.log(3);
    let p = new Promise((resolve, reject) => {
        console.log(7);
        setTimeout(() => {
            console.log(5);
            resolve(6);
        }, 0)
        resolve(1);
    });
    resolve(2);
    p.then((arg) => {
        console.log(arg);
    });
 
}));
 
first().then((arg) => {
    console.log(arg);
});
console.log(4);


//1.同步的代码(最高)
//3
//7
//4

//2. 微任务的异步代码(次高,then)

//1
//2


//3. 宏任务的异步代码(最低,setTimeout)
//5

//6 不执行
  1. then 的第二回调函数和 catch

交叉,继承

// 画出以下异步函数执行的可能的路线。
// https://developers.google.com/web/fundamentals/primers/promises
// 蓝线表示执行的 promise 路径,红路表示拒绝的 promise 路径。
// https://developers.google.com/web/fundamentals/primers/imgs/promise-flow.svg
asyncThing1()
  .then(function() {
    return asyncThing2();
  })
  .then(function() {
    return asyncThing3();
  })
  .catch(function(err) {
    return asyncRecovery1();
  })
  .then(
    function() {
      return asyncThing4();
    },
    function(err) {
      return asyncRecovery2();
    }
  )
  .catch(function(err) {
    console.log("Don't worry about it");
  })
  .then(function() {
    console.log("All done!");
  });

image-20200215153639230

链式调用

b.then().then().then()

返回自己,或者返回一个 和自己类似的结构

  
class Test1{

  then(){
    console.log(6666);
    return this;
  }
}

var a= new Test1();
a.then().then().then()

class Test2{
  then(){
    console.log(77777);
    return new Test2();
  }
}

var b= new Test2();
b.then().then().then()
单元测试

1.准备测试框架

参考jest 官网

# 1.初始化项目
npm init

# 2.安装 jest
yarn add --dev jest
#npm install --save-dev jest

# 3. 支持 es2015+ 和 ts
yarn add --dev babel-jest @babel/core @babel/preset-env @babel/preset-typescript @types/jest


# 4. 添加 文件 babel.config.js
// babel.config.js
module.exports = {
  presets: [
    ['@babel/preset-env', {targets: {node: 'current'}}],
     '@babel/preset-typescript',
  ],
};

# 5.配置命令
    "test": "jest",
    "test:watch": "jest --watchAll"

2.为了检查 Promise 实现的正确性,我们提前准备好单元测试

  
import MyPromise from "./index";

// 1
test("1.promise 参数函数会立即执行", function() {
  var string;

  new MyPromise(function() {
    string = "foo";
  });

  expect(string).toBe("foo");

},500);


it("2. promise 在 then 的回调函数中可以拿到 resolve 的数据。", function(done) {
  var testString = "foo";

  var promise = new MyPromise(function(resolve) {
    setTimeout(function() {
      resolve(testString);
    }, 20);
  });

  promise.then(function(string) {
    expect(string).toBe(testString);
    done();
  });
},500);

// 3
it("promise 可以有多个 then,并且会依次执行", function(done) {
  var testString = "foo";

  var promise = new MyPromise(function(resolve) {
    setTimeout(function() {
      resolve(testString);
    }, 20);
  });

  promise.then(function(string) {
    expect(string).toBe(testString);
  });

  promise.then(function(string) {
    expect(string).toBe(testString);
    done();
  });
},500);


it("4.promise 可以嵌套多个 then,then的回调中可以返回 promise ", function(done) {
  var testString = "foo";

  var promise = new MyPromise(function(resolve) {
    setTimeout(function() {
      resolve();
    }, 20);
  });

  promise
    .then(function() {
      return new MyPromise(function(resolve) {
        setTimeout(function() {
          resolve(testString);
        }, 20);
      });
    })
    .then(function(string) {
      expect(string).toBe(testString);
      done();
    });
},500);

// 5
it("5.promise 可以嵌套多个 then,then的回调中可以返回 一个普通值", function(done) {
  var testString = "foo";

  var promise = new MyPromise(function(resolve) {
    setTimeout(function() {
      resolve();
    }, 20);
  });

  promise
    .then(function() {
      return testString;
    })
    .then(function(string) {
      expect(string).toBe(testString);
      done();
    });
},500);

// 6
it("6.resolved 状态的promise ,如果调用 then 方法会立即执行", function(done) {
  var testString = "foo";

  var promise = new MyPromise(function(resolve) {
    setTimeout(function() {
      resolve(testString);
    }, 20);
  });

  setTimeout(function() {
    promise.then(function(value) {
      expect(value).toBe(testString);
      done();
    });
  }, 200);

},500);


it("7. 二次调用 resolve 不会产生影响。", function(done) {
  var testString = "foo";
  var testString2 = "bar";

  var promise = new MyPromise(function(resolve) {
    setTimeout(function() {
      resolve(testString);
      resolve(testString2);
    }, 20);
  });

  promise.then(function(value) {
    expect(value).toBe(testString);
  });

  setTimeout(function() {
    promise.then(function(value) {
      expect(value).toBe(testString);
      done();
    });
  }, 50);

},500);
实现promise

promise 是一个对象,一般是通过 new Promise ()来实例化的;所以这里我要实现 Promise 类!

promise 的 then 是可以链式调用的,所以可能会用到上面提到的,链式调用的实现。

根据逐个单元测试的要求来实现 Promise

主要实现 Promise 的构造方法和 then 方法; 后面会以链接的方式给出完整的实现。

【解题代码】

const State = {
  pending: "pending",
  resolved: "rejected",
  rejected: "rejected"
};

const noop = () => {};

class MyPromise {
  constructor(exclutor) {
    exclutor(this._resolve.bind(this), this._reject);
  }
  _state = State.pending;
  _value;
  _resolve(val) {
    if (this._state === State.pending) {
      this._value = val;
      this._state = State.resolved;

      this._runResolveArray();
    }
  }
  _reject() {}
  _runResolveArray() {
    //执行 then 传入进来的 onRes
    this._resArray.forEach(item => {
      // const item
      const result = item.handle(this._value);
      const nextPromise = item.promise;

      if (result instanceof MyPromise) {
        result.then(val => item.promise._resolve(val));
      } else {
        item.promise._resolve(result);
      }
    });
  }

  _resArray = [];

  then(onRes, onRej = noop) {
    // if (this._state === State.pending) {
    const newPromise = new MyPromise(() => {});
    const item = { promise: newPromise, handle: onRes };
    this._resArray.push(item);

    // }
    if (this._state === State.resolved) {
      this._runResolveArray();
    }
    return newPromise;
  }
}

export default MyPromise;


【问题延伸】

几种Promise 的实现

https://github.com/ericyang89/my-promise

https://github.com/vividbytes/implementing-promises

https://github.com/iam91/zpromise/blob/master/src/zpromise.js

代理

TS

好处: 比js多了类型约束,减少bug

应用:vue和react都可使用

模块封装

网络/api 请求模块
  1. 接口请求一般是异步的,可以返回 promise 更加清晰。

  2. 网络请求url 的公共部分可以单独配置到 网络请求内部。

  3. 针对所有的接口可以进行统一的处理。这也是面向切面编程的一个实践。

  4. 可以借助第三方库 axios 快速的封装 网络请求模块。

【代码】

import axios from "axios";
import constant from "../constant";
import reactNavigationHelper from "./reactNavigationHelper";
import commonToast from "./commonToast";

//配置请求url 的公共部分及超时时间
const commonHttp = axios.create({
  baseURL: constant.baseUri,
  timeout: 10 * 1000
});

commonHttp.interceptors.response.use(
  function(response) {
    return response;
  },
  function(error) {
    //针对所有接口统一处理登录过期的问题
    if (error.response.status === 401) {
      commonToast.show("登录过期");
      reactNavigationHelper.navigate("Login");
    }
    return Promise.reject(error);
  }
);

export default commonHttp;

导入

js

js导入js

import mycounter from ‘./counter’

console.log(mycounter) 可以

但是不能mycounter += 1,即 mycounter = mycounter + 1,会出现mycounter未定义

可以用,但没有定义变量

var mycounter = mycounter + 1, var变量提升,原来import的没用了,mycounter变为undefined,+1,输出NaN

vue
  1. vue页面的script

1)script导入js文件

import 文件名 from ‘@/utils/文件名.js’

2)script导入vue文件,组件(注册)

import 组件名 from ‘./组件名’

export default{

​ components:{

​ 组件名

​ }

}

  1. vue页面的template

    导入组件<组件名 />

    与uniapp些许不同,uniapp还是双标签

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
毕设新项目-基于Java开发的智慧养老院信息管理系统源码+数据库(含vue前端源码).zip 【备注】 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用!有问题请及时沟通交流。 2、适用人群:计算机相关专业(如计科、信息安全、数据科学与大数据技术、人工智能、通信、物联网、自动化、电子信息等)在校学生、专业老师或者企业员工下载使用。 3、用途:项目具有较高的学习借鉴价值,不仅适用于小白学习入门进阶。也可作为毕设项目、课程设计、大作业、初期项目立项演示等。 4、如果基础还行,或热爱钻研,亦可在此项目代码基础上进行修改添加,实现其他不同功能。 欢迎下载!欢迎交流学习!不清楚的可以私信问我! 毕设新项目-基于Java开发的智慧养老院信息管理系统源码+数据库(含vue前端源码).zip毕设新项目-基于Java开发的智慧养老院信息管理系统源码+数据库(含vue前端源码).zip毕设新项目-基于Java开发的智慧养老院信息管理系统源码+数据库(含vue前端源码).zip毕设新项目-基于Java开发的智慧养老院信息管理系统源码+数据库(含vue前端源码).zip毕设新项目-基于Java开发的智慧养老院信息管理系统源码+数据库(含vue前端源码).zip毕设新项目-基于Java开发的智慧养老院信息管理系统源码+数据库(含vue前端源码).zip毕设新项目-基于Java开发的智慧养老院信息管理系统源码+数据库(含vue前端源码).zip毕设新项目-基于Java开发的智慧养老院信息管理系统源码+数据库(含vue前端源码).zip毕设新项目-基于Java开发的智慧养老院信息管理系统源码+数据库(含vue前端源码).zip
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值