前端常用知识点

398 阅读29分钟

Object

:boom:对象里的属性其实是有内部特征的,这些特性有JavaScript引擎规范定义,所以开发中不能直接访问这些特性,内部特性一般会用两个中括号把名称括起来,如[[Enumerable]],对象里的属性的特征分为两个,分别是 数据的属性和访问器的属性

:boom:数据属性

:o:读取数据属性,用Object.getOwnPropertyDescriptor(obj,'name')

let obj = {
            name: 'dd'
        }
console.log(Object.getOwnPropertyDescriptor(obj,'name'))
// 输出结果
{
  configurable: true
  enumerable: true
  value: "dd"
  writable: true
}
[[Configurable]]:表示属性是否可以通过 delete 删除并重新定义,是否可以修改它的特
性,以及是否可以把它改为访问器属性。默认情况下,所有直接定义在对象上的属性的这个特
性都是 true

[[Enumerable]]:表示属性是否可以通过 for-in 循环返回。默认为true(可枚举)

[[Writable]]:表示属性的值是否可以被修改。默认为 true

[[Value]]:包含属性实际的值。这就是前面提到的那个读取和写入属性值的位置。默认值为 undefined

:o:修改数据属性 Object.defineProperty(obj,'属性名',配置对象)如果要删除对象向的某个属性,要用delete关键字,而不能直接设置属性值为undefined,不然该属性依然在,只不过值为undefined而已

let obj = {
            name: 'dd'
        }
        Object.defineProperty(obj,'name',{
            writable: false
        })
        obj.name = 'haha'
        console.log(obj.name)  //dd

        // 需要特别注意的是 [[Configurable]],这特性一旦被修改为false,结果就固定下次再通过 Object.defineProperty 来修改这些特性就会报错,且这时再用 delete关键字来删除属性就会失效

        // 结果1
        let obj = {name:'dd'}
        Object.defineProperty(obj, 'name', {
            // configurable: false,
        })
        delete obj.name;
        console.log(obj.name) //undefine 能用delete关键字正常删除

        //结果2
        let obj = {name:'dd'}
        Object.defineProperty(obj, 'name', {
            configurable: false,
        })
        delete obj.name;
        console.log(obj.name) //dd delete关键字删除属性无效

:o:用Object.defineProperty(obj,'属性名',配置对象)给对象添加新属性

let obj = {}
Object.defineProperty(obj, 'name', {
    configurable: true,
    enumerable: true,
    value: "dd",
    writable: true
})
console.log(obj.name) //dd

Vue2数据响应原理

:boom:访问器属性(重点)

访问器属性不包含数据值。相反,它们包含一个获取(getter)函数和一个设置(setter)函数,不 过这两个函数不是必需的。在读取访问器属性时,会调用getter函数,在修改值时会调用setter函数 问器属性有 4 个特性描述它们的行为。

 [[Configurable]]:表示属性是否可以通过 delete 删除并重新定义,是否可以修改它的特 性,以及是否可以把它改为数据属性。默认情况下,所有直接定义在对象上的属性的这个特性 都是 true。  [[Enumerable]]:表示属性是否可以通过 for-in 循环返回。默认情况下,所有直接定义在对 象上的属性的这个特性都是 true。  [[Get]]:获取函数,在读取属性时调用。默认值为 undefined。  [[Set]]:设置函数,在写入属性时调用。默认值为 undefined。

:o:设置访问器属性只能用Object.defineProperty()

let obj2 = {
            kkName: 'song'
        }
        let obj = {
            name: 'dd',
            age: 16
        }
        Object.defineProperty(obj,'name',{
           get(){
               return obj2.kkName
           },
           set(value){
               //this.name = value//报错,不能修改监听的这个属性
               //this.age = value //可行
                obj2.kkName = value
           }
        })
        console.log(obj.name)  //男
        obj.name = 'ggg'
        console.log(obj2.kkName)  //男
重点:根据上面这个访问器属性可以用来做一些代理,如上面 用 obj 来代理 obj2,读取obj实际读取到的是obj2的属性,更改obj属性,实际更改的obj2的属性,vue2数据的响应式原理就是用了这个
Object.defindProperty()来代理监听模板上的数据,只要data中的数据发生更改啥的,模板上就能响应起来

Vue3数据响应原理

:boom:Vue2数据响应式原理有很多缺点,比如只能代理监听 读取和修改,删除属性和添加属性无法响应代理,还有一个就是太繁琐,每个属性都得设置getter和setter,重复代码太多。Vue3的数据响应式采用的是window身上的内置对象Proxy(代理)和Reflect(反射,反射的作用就是相比直接读取,它更容易捕捉错误,也不容易一出错就停止往下执行)!一套设置代理整个对象,增删改查都能处理。应用场景:当需要暴露某个对象又不想用户对他进行过度操作,需要限制时就可以用proxy代理,将代理对象暴露出去,用户的一些操作是否合格在代理对象里进行判断等相关操作

let obj = { name: 'lisi', gender: '男'}
        // 用 p 对象来代理 obj 对象,任何对 p 的增删改查操作都会反映到 obj 身上
        let p = new Proxy(obj,{
            get(target,propName){ //target为代理的这个对象 obj,propName为需要操作的属性名
                //console.log(target,propName) //{name: 'lisi', gender: '男', age: 18},'name'
                
                //return target[propName]// 这样直接读取也行,但Vue3采用的是用反射对象进行读取
                return Reflet
            },
            // 不仅修改会调用,添加新属性操作也会调用
            set(target,propName,value){
                // target[propName] = value
                Reflect.set(target,propName,value)

            },
            deleteProperty(target,propName){
                // return delete target[propName]  //delete target[propName]成功会返回true
                return Reflect.deleteProperty(target,propName)
            }
        })
        console.log(p.name) //lisi
        p.name = 'zhangsan'
        console.log(obj.name) //zhangsan
        p.age = 18
        console.log(obj) //{name: 'zhangsan', gender: '男', age: 18}
        console.log(delete p.age) //true    
        console.log(obj) //{name: 'zhangsan', gender: '男'}

说一下eventloop

js是单线程的,js在执行代码的时候会将代码分为同步任务和异步任务,同步任务在主线程中执行完后再去执行异步任务,异步代码又分为宏任务(setTimeout、setInterval、setImmediate、Ajax、DOM事件)和微任务(process.nextTick、MutationObserver、Promise.then catch finally),先按出场顺序以队列的形式把微任务执行了,然后在执行宏任务,在执行宏任务的过程同理将同步代码执行完后有重复刚才操作

typeof 与 instanceof

:boom:javascript 有 8 中数据类型,其中有七种基本类型和一种引用类型,分别是: string、number、Boolean、symbol、bigint、undefined、null、object

:boom:bigint(ES11 新增类型):JS 提供 Number.MAX_SAFE_INTEGER(9007199254740991(2^53-1))常量来表示 最大安全整数,Number.MIN_SAFE_INTEGER(-9007199254740991 (-(2^53-1)))常量表示最小安全整数,JS 中的 Number 类型只能安全地表示-9007199254740991 (-(2^53-1)) 和 9007199254740991(2^53-1)之间的整数,任何超出此范围的整数值都可能失去精度,为了能让这范围之外的整数不失精度,有了 Bigint 类型。

创建 bigint 类型

//bigint只能和同类型比较大小,不能和其他类型进行比较
要创建BigInt,只需在整数的末尾追加n即可。比较:
console.log(9007199254740995n);    // → 9007199254740995n
console.log(9007199254740995);     // → 9007199254740996
或者,可以调用BigInt()构造函数
BigInt("9007199254740995");    // → 9007199254740995n

:o:typeof 操作符可以用来确定数据的类型,返回字符串

typeof 1 // 'number'
typeof '1' // 'string'
typeof undefined // 'undefined'
typeof true // 'boolean'
typeof Symbol() // 'symbol'
typeof 1n //bigint
typeof console.log // 'function'
typeof null // 'object'
typeof [] // 'object'
typeof {} // 'object'
typeof console // 'object'
typeof /123/g //'object'

// 总结
注意:null并非引用类型object,这是一个bug,null被认为是对一个空对象的引用

由上可以总结:typeof 除了判断 null 、数组 、对象 返回 object外,其他类型都可以判断,且可以判断出是否为 function
// 习题
console.log(typeof typeof []) //string,它会从右边开始判断,所以永远是string
console.log(typeof x) //undefined ,因为x未定义

:o:instanceof

:boom:instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上

function aa() {}
console.log({} instanceof Object) //true
console.log([] instanceof Object) //true 特别注意
console.log([] instanceof Array) //true
console.log(aa instanceof Function) //true
console.log(null instanceof Object) //false
console.log(123 instanceof Number) //false
console.log('ji' instanceof String) //false
// 定义构建函数
let Car = function () {}
let benz = new Car()
benz instanceof Car // true
let car = new String('xxx')
car instanceof String // true
let str = 'xxx'
str instanceof String // false

:o:数组?

:boom:方式一:console.log(Array.isArray([]))//true

:boom:方式二:console.log([] instanceof Array)//true

:boom:instanceof 原理:实例对象的原型属性和构造对象的原型属性指向同一个对象,Object.getPrototypeOf()可以拿到一个对象的原型

let proto = Object.getPrototypeOf(left)
if (proto === right.prototype) return true
function myInstanceof(left, right) {
  // 这里先用typeof来判断基础数据类型,如果是,直接返回false
  if (typeof left !== 'object' || left === null) return false
  // getProtypeOf是Object对象自带的API,能够拿到参数的原型对象
  let proto = Object.getPrototypeOf(left)
  while (true) {
    if (proto === null) return false
    if (proto === right.prototype) return true //找到相同原型对象,返回true
    proto = Object.getPrototypeof(proto)
  }
}

:o:对象?

:boom:方式一:Object.prototype.toString.call({}) // "[object Object]",这是唯一标识,不会错

:boom:方式二:

function typeofObj(obj) {
  let type = typeof obj
  if (type !== 'object' || obj === null) return false
  return obj instanceof Array ? false : true
}
console.log(typeofObj({})) //true
console.log(typeofObj([])) //false
console.log(typeofObj(null)) //false
console.log(typeofObj(123)) //false

:o:空对象?

:boom:方式一:JSON.stringify(obj) === '{}'

:boom:方式二

var obj = {}
var b = function () {
  for (var key in obj) {
    return false
  }
  return true
}
alert(b()) //true

:boom:方式三 Object.getOwnPropertyNames()方法

此方法是使用Object对象的getOwnPropertyNames方法,获取到对象中的属性名,存到一个数组中,返回数组对象,我们可以通过判断数组的length来判断此对象是否为空
注意:此方法不兼容ie8,其余浏览器没有测试
var data = {};
var arr = Object.getOwnPropertyNames(data);
alert(arr.length == 0);//true

:boom:方式四 使用 ES6 的 Object.keys()方法

ES6的新方法, 返回值也是对象中属性名组成的数组
var data = {}
var arr = Object.keys(data)
alert(arr.length == 0) //tru

:o:对象含?属性

:boom:判断一个对象自身是否含有某样属性

:boom:方式一 实例对象.hasOwnProperty() (常用于深浅拷贝时判断,因为我们往往只需拷贝对象自身的属性,而用 in 操作符会把其原型上的属性也遍历出来)

function obj() {
  this.age = 12
}
obj.prototype.name = 'jj'
let obj2 = new obj()
console.log(obj2.hasOwnProperty('age')) //true
console.log(obj2.hasOwnProperty('name')) //true

:boom:方式二 用 in 关键字,用法 属性名 in 对象 返回 true 或 false(当需要个某对象添加属性时,常用 in 先做判断,防止同事已添加过而覆盖)

var obj = {};
console.log('name' in obj) //false

缺点:
function obj(){this.age = 12}
obj.prototype.name = 'jj'
let obj2 = new obj()
console.log('age' in obj2) //true
console.log('name' in obj2) //true
总结:如果该属性不在对象自身上,在原型上他也会返回true

:boom:方式三

let obj = {}
if(!obj.name){
    console.log("不含name属性")
}
缺点:如果刚好有个有个name属性值为undefined,就杯具了

通用类型判断

:boom:采用 Object.prototype.toString,调用该方法,统一返回格式“[object Xxx]”的字符串

Object.prototype.toString({}) // "[object Object]"
Object.prototype.toString.call({}) // 同上结果,加上call也ok
Object.prototype.toString.call(1) // "[object Number]"
Object.prototype.toString.call('1') // "[object String]"
Object.prototype.toString.call(true) // "[object Boolean]"
Object.prototype.toString.call(function () {}) // "[object Function]"
Object.prototype.toString.call(null) //"[object Null]"
Object.prototype.toString.call(undefined) //"[object Undefined]"
Object.prototype.toString.call(/123/g) //"[object RegExp]"
Object.prototype.toString.call(new Date()) //"[object Date]"
Object.prototype.toString.call([]) //"[object Array]"
Object.prototype.toString.call(document) //"[object HTMLDocument]"
Object.prototype.toString.call(window) //"[object Window]"

:o:实现一个全局通用的数据类型判断方法

function getType(obj) {
  let type = typeof obj
  if (type !== 'object') {
    // 先进行typeof判断,如果是基础数据类型,直接返回
    return type
  }
  // 对于typeof返回结果是object的,再进行如下的判断,正则返回结果
  return Object.prototype.toString.call(obj).replace(/^\[object (\S+)\]$/, '$1')
}
getType([]) // "Array" typeof []是object,因此toString返回
getType('123') // "string" typeof 直接返回
getType(window) // "Window" toString返回
getType(null) // "Null"首字母大写,typeof null是object,需toString来判断
getType(undefined) // "undefined" typeof 直接返回
getType() // "undefined" typeof 直接返回
getType(function () {}) // "function" typeof能判断,因此首字母小写
getType(/123/g) //"RegExp" toString返回

Boolean判断

:boom:可以用 Bookean()进行转换

:o:判断于转换

转化为 true转化为 false
truefalse
非空字符串""(空字符串)
非零数值(包括负数和无穷值)0、NaN
任意对象(包括空对象和空数组)null
非 0n(包括负的 bigint 类型)0n
N/A(不存在)undefined

相等符

:boom: ==操作符会先进行强制类型转换再进行比较

:boom:总结几点:

:o:如果任一方是 Boolean,则会先转换 Boolean 再比较,true 为 1false 为 0

:o:如果任一方是对象,则先调用对象的 valueOf()方法取得其原始值,在根据规则比较

:o:如果一个操作数是字符串,一个是数值,则会尝试将字符串转换成数值再进行比较

:o:NaN 和任何类型都不相等,包括和 NaN 本身(===也一样)

:o:null 和 undefined 相等

:o:null 和 undefined 无法再转成其他类型

示例结果
null == undefinedtrue
"NaN" == NaNfalse
5 == NaNfalse
NaN == NaNfalse
NaN != NaNtrue
false == 0true
true == 1true
true == 2false
undefined == 0false
null == 0false
"5" == 5true

:boom:NaN: NaN 属性是代表非数字值的特殊值。该属性用于指示某个值不是数字。方法 parseInt() 和 parseFloat() 在不能解析指定的字符串时就返回这个值。

提示:请使用 isNaN() 全局函数来判断一个值是否是 NaN 值。

console.log(Number.NaN) //NaN
console.log(parseInt('dd123')) //NaN
console.log(parseInt('123dd')) //123
console.log(isNaN('df123')) //true
console.log(isNaN('123')) //false

深浅拷贝

:o:浅拷贝

:boom:问题描述:当要将一个对象进行拷贝且使和拷贝出的对象完全独立互不干扰时往往是通过对原对象进行遍历属性复制的方法,如下示例

 var obj1 = {
    name: 'zhangsan',
    age: '18',
    language: [1, [2, 3], [4, 5]],
    hobby:{type:'休闲',name:'游泳'},
    fn: function aa() {console.log(2)}
};
let obj2 = {}
for (const key in obj1) {
    if(Object.hasOwnProperty(key)){//hasOwnProperty用来判断是不是这个对象身上的属性而不是原型上的
        obj2[key] = obj1[key]
    }
}
//修改
obj2.language[0] = 6
obj2.name = 'lizi'
obj2.hobby.name = '羽毛球'
obj2.fn =function bb() {console.log(33)}
//结果
console.log(obj1.name)//zhangsan
console.log(obj1.language[0])//1
console.log(obj1.hobby)//{type:'休闲',name:'羽毛球'}
console.log(obj1.fn)//function aa() {console.log(2)}

console.log(obj2.name)//lizi
console.log(obj2.language[0])//6
console.log(obj2.hobby)//{type:'休闲',name:'羽毛球'}
console.log(obj2.fn)//function bb() {console.log(33)}
// 结论
由上输出结果可以看出obj1和obj2并非完全独立,基本数据类型属性的复制不会相互影响,但引用类型数组和对象属性拷贝到新对象后他们还是相互影响,他们指向同一地址,所以其一改变另一也跟着改变,这里引用类型函数需要注意,因为这类的 fn 属性重新赋值后会将原来的函数地址覆盖,所以fn属性是独立的
综合以上这种不完全的拷贝就叫“浅拷贝”

:boom:浅拷贝实现方式:

  1. 对原对象进行遍历属性拷贝到新对象
  2. 用 Js 中的 API Object.assign() (assign:分配)
const target = { a: 1, b: 2 }
const source = { b: 4, c: 5 }
const returnedTarget = Object.assign(target, source)
console.log(target) //{ a: 1, b: 4, c: 5 }
console.log(returnedTarget) //{ a: 1, b: 4, c: 5 }
const returnedTarget = Object.assign({}, source)
console.log(returnedTarget) //{ b: 4, c: 5 }
  1. 解构的方式 let obj2 = {...obj1}

:o:深拷贝

:boom:和浅拷贝不同,深拷贝会实现两个对象的完全独立,即使是引用类型的属性也不会相互影响

  1. 用工具包 ladash
//nodejs
const _ = require('lodash')
const obj1 = {
  a: 1,
  b: { f: { g: 1 } },
  c: [1, 2, 3],
}
const obj2 = _.cloneDeep(obj1)
console.log(obj1.b.f === obj2.b.f) // false

//vue中
全局引入
import _ from 'lodash'
Vue.prototype._ = _
  1. 用 jQuery
const $ = require('jquery')
const obj1 = {
  a: 1,
  b: { f: { g: 1 } },
  c: [1, 2, 3],
}
const obj2 = $.extend(true, {}, obj1)
console.log(obj1.b.f === obj2.b.f) // false
  1. 用 JSON.parse 和 JSON.stringify
const obj2=JSON.parse(JSON.stringify(obj1));
缺点:会忽略 functionundefined、symbol
  1. 手动递归遍历
function copy(src) {
  if (typeof src === 'object' && src) {
    let target = Array.isArray(src) ? [] : {}
    for (const key in src) {
      target[key] = copy(src[key])
    }
    return target
  } else {
    return src
  }
}

数组去重

:o:forof和includes

function unique(arr) {
   if (!Array.isArray(arr)) return false
   let arr2 = [];
   for (const item of arr) {
       if (!arr2.includes(item)) {
           arr2.push(item)
       }
   }
   return arr2
}

:o:通过 Set

:boom:可以生成没有重复项的 Set 接口,再将 set 结构转化为数组结构

let RemoveRepeat = (arr) => [...new Set(arr)]
// 或者
let RemoveRepeat = (arr) => Array.from(new Set(arr)) //Array.from可以将Set结构转化为数组结

Set 与 Map 详情

:o:通过 filter 和 indexOf

let arr = [3, 3, 4, 2, 65, 3, 3, 2, 2]

let aa = arr.filter((item, index) => {
  return arr.indexOf(item) === index
})

:o:reduce 和 indexOf 和 concat

let arr = ["dd","dd",'tt',"tt","ee","rr",'dte','dd']
    let newarr =  arr.reduce((pre,next,index,arr) => {
        if(!pre.includes(next)){
            pre.push(next)
        }
        return pre
    },[])
    console.log(newarr) //["dd",'tt',"ee","rr",'dte']
let arr = [3, 3, 4, 2, 65, 3, 3, 2, 2]
let aa = arr.reduce((pre, next, i) => {
  // 在这里pre被封装成数组,next是arr元素,数组元素和基本类型元素都能被concat合并
  console.log(pre) //第八次[3, 4, 2, 65]
  return [].concat(pre, arr.indexOf(next) === i ? next : [])//说明:每次循环都会将return的值赋给pre
})

// reduce介绍
const array1 = [1, 2, 3, 4];

// 0 + 1 + 2 + 3 + 4
const init = 0; //如果有传init参数只传一个迭代函数第一轮遍历pre=0,cur=1,第二轮遍历pre=0+1,cur=3,有传第一次迭代pre=init,cur=数组第一项
const sumWithInitial = array1.reduce(
  (pre, cur) => pre + cur,
  init
);

console.log(sumWithInitial);//10

统计数组元素个数

:boom:因为reduce函数参数中有传入第二个参数,所以首次遍历pre为传入的第二个参数 {},next为'dd',第二次遍历pre为{dd: {name:'dd',count:1}},next为'tt'

// 统计数组中每个元素的个数
    let arr = ["dd",'tt',"dd","tt","ee","rr",'dte','dd']
    let newobj =  arr.reduce((pre,next,index,arr) => {
        if(!pre[next]){
            pre[next] = {name:next,count:1}
        }else{
            pre[next].count++
        }
        return pre
    },{})
    console.log(newobj)  //{dd: {name:'dd',count:3}, tt: {…}, ee: {…}, rr: {…}, dte: {…}}

执行上下文

:boom:执行上下文是一个抽象概念,用来比喻代码运行的环境,全局执行上下文和函数执行上下文,全局上下文在创建的时候 this 会指向全局对象(浏览器中就是 window)还有一个 evel()函数也有属于自己的执行上下文。

Js 代码在解析执行的时候会创建一个调用栈(执行上下文栈)这是一个先进后出的数据结构栈,首先会将全局上下文压入栈底,当代码执行到有函数被调用时会为该函数创建一个函数上下文压入栈顶,如果在执行这个函数时内部又调用了新函数,会为这个子函数创建函数上下文并又压入栈顶,直到子函数运行完毕弹出栈再继续往下执行父函数,父函数执行完毕又弹出栈,最后是所有代码执行完毕全局上下文弹出栈

作用域

:boom:作用域就是变量和函数的有效区域。可以分为全局作用域,函数作用域和块级作用域。

:boom:全局作用域:不在函数或大括号中的变量都属于全局作用域中的变量,任何位置都能访问这些变量

:boom:函数作用域:在函数内部声明的变量就属于函数作用域,外部不能访问(闭包除外)

:boom:块级作用域:ES6 引入 let 和 const 之后有了块级作用域,大括号中的变量都属于块级作用域中的变量,外部不能范围

:boom:词法作用域:又叫静态作用域,变量或函数在被定义时就确定好了而非执行时,就是说在我们写代码时就已经确定好了,Js 是遵循词法作用域的,看下面例子,词法作用域的效果,不是闭包

1 let a = 'dd'
2 function ee(){
3    console.log(a)
4 }
function rr(){
    let a = 'jj'
    ee()
}
rr()//'dd'
// 输出dd而非jj,因为在写完1到4行代码后ee函数里的a变量就已经被确定为dd了,而非在rr里执行调用ee函数时

:boom:Js 在解析执行到某个变量时首先会在当前作用域下查找,如果没找到就继续向外一次查找,直到到全局作用域下还是没找到的话就报错,作用域链是由内向外查找的,无法从外向内查找

闭包

:boom:有权访问另外一个函数作用域中的变量的函数

:boom:作用:1.可以保存私有变量避免污染全局 2.可以延长变量的生命周期

:boom:缺点:容易造成内存泄漏

闭包笔记摘抄至

:boom:使用场景如下:

// 用来保存变量,比如当需要统计点击次数的时候
function count() {
  var num = 0
  return function () {
    return ++num
  }
}
var getNum = count() // 第一个需要统计的地方
var getNewNum = count() //第二个需要统计的地方
// 如果我们统计的是两个button的点击次数
document.querySelectorAll('button')[0].onclick = function () {
  console.log('点击按钮1的次数:' + getNum())
}
document.querySelectorAll('button')[0].onclick = function () {
  console.log('点击按钮2的次数:' + getNewNum())
}

// 2.函数柯里化,函数柯里化(currying)又称部分求值。一个 currying 的函数首先会接受一些参数,接受了这些参数之后,该函数并不会立即求值,而是继续返回另外一个函数,刚才传入的参数在函数形成的闭包中被保存起来。待到函数被真正需要求值的时候,之前传入的所有参数都会被一次性用于求值。
function makeAdder(x) {
  return function (y) {
    return x + y
  }
}
var add5 = makeAdder(5)
var add10 = makeAdder(10)
console.log(add5(2)) // 7
console.log(add10(2)) // 12
add5 = null // 释放对闭包的引用
console.log(add5(1)) //Uncaught TypeError: add5 is not a function

//3.IIFE(自执行函数)
var add = (function () {
  var counter = 0
  return function () {
    return (counter += 1)
  }
})()
add() // 1
add() // 2
add() // 3

//4.IIFE(自执行函数)
for (var i = 0; i < 5; i++) {
  ;(function (j) {
    setTimeout(() => {
      console.log(j)
    }, j * 1000)
  })(i)
}

// 5.防抖
function debounce(fn, wait = 50) {
  let timer
  return function () {
    if (timer) clearTimeout(timer)
    timer = setTimeout(() => {
      fn.apply(this, arguments)
    }, wait)
  }
}
// 节流
function throttle(fn) {
  let canRun = true
  return function () {
    if (!canRun) return
    canRun = false
    setTimeout(() => {
      fn.apply(this, arguments)
      canRun = true
    }, 1000)
  }
}

// 7.释放内存
function fn2() {
  let test = new Array(1000).fill('rocky')
  return function () {
    console.log(test)
    return test
  }
}
let fn2Child = fn2()
fn2Child()
fn2Child = null // 置空

// 闭包题
var data = [];
for (var i = 0; i < 3; i++) {
  data[i] = function () {
    console.log(i);
  };
}
data[0]();//3
data[1]();//3
data[2]()//3

:o:注意

:boom:词法作用域:又叫静态作用域,变量或函数在被定义时就确定好了而非执行时,就是说在我们写代码时就已经确定好了,Js 是遵循词法作用域的,看下面例子,词法作用域的效果,不是闭包

1 let a = 'dd'
2 function ee(){
3    console.log(a)
4 }
function rr(){
    let a = 'jj'
    ee()
}
rr()//'dd'
// 输出dd而非jj,因为在写完1到4行代码后ee函数里的a变量就已经被确定为dd了,而非在rr里执行调用ee函数时

防抖与节流

:o:防抖

:boom:防抖(debounce): n 秒后在执行该事件,若在 n 秒内被重复触发,则重新计时

:boom:使用场景:window.resize 事件(窗口大小改变)、表单的输入、表单的提交,防止用户不断提交表单

function debounce(fn,delay=500){
  let timer;
  return function(...args)(
    cleanTimeout(timer)
    timer = setTimeout(() => {
      fn.apply(this,args)
    },delay)
  )
}
//完整例子
let father = document.getElementById('father')
function debounce(fn,delay=500){
   let timer = null;
   return function(...args){
       clearTimeout(timer)
       timer = setTimeout(() => {
           fn.apply(this,args) //arus为数组
           // clearTimeout(timer)
       }, delay);
   }
}
function sentHttp(item){
  console.log( Array.isArray(item))
   console.log(item)
}
let func =  debounce(getValeu,500)
father.addEventListener('scroll',function(event){
   func(event.target.innerHTML,'dd')
})

:o:节流

:boom:节流(throttled): 不管操作多么频繁,固定每 n 秒后触发一次

:boom:使用场景:鼠标的滚轮事件、鼠标的移动事件(对象 document)、鼠标的不断点击事件

function throttled(fn, delay = 500) {
  timer = null
  return function (...args) {
    if (!timer) {
      //产生闭包
      timer = setTimeout(() => {
        fn.apply(this, args) //由于是箭头函数,所以这里的this指向外层,所以直接写this就行,args为数组
        timer = null //这里不能换成clearTimeout(timer),因为clearTimeout(timer)会将timer变量彻底清除
      }, delay)
      // timer从调用定时器先获得值之后先执行这里,以为定时器是异步的,所以在定时器时间未到之前timer是有值的,所以只进入了一次if判断,直到
    }
  }
}

//绑定元素完整例子
let father = document.getElementById('father')
function throttled(fn, delay = 500) {
  let timer = null
  return function (...args) {
    if (!timer) {
      timer = setTimeout(() => {
        fn.apply(this, args) //arus为数组
        timer = null
        // clearTimeout(timer)
      }, delay)
    }
  }
}
function sendHTTP(item) {
  console.log(Array.isArray(item))
  console.log(item)
}
let func = throttled(sendHTTP, 500)
father.addEventListener('scroll', function (event) {
  func(event.target.innerHTML, 'dd')
})

new 过程

用 new 操作符调用构造函数会执行下面操作

  1. 内存中会创建一个新对象
  2. 对象的原型属性指向构造函数的原型属性
  3. 将构造函数中的 this 赋值给这个新对象
  4. 执行函数中的代码
  5. 如果没有返回其他值则某人返回这个新对象
// new实现 1
function myNew(fun, ...args) {
    let obj = {}
    obj.__proto__ = fun.prototype
    fun.call(obj,...args)
    return obj
}

// 方式2
function myNew(fun, ...args) {
    let obj = Object.create(fun.prototype)
    fun.call(obj,...args)
    return obj
}

原型链与继承

:boom:原型链:当访问属性变量时首先会先去该变量所绑定的对象身上找,当找不到的时候会继续向这个对象的原型对象上去寻找,找到就返回值,找不到就报错,这中链式的寻找路径叫原型链 原型链查看详情

继承

:boom:继承:继承就是子类给以继承父类的许多方法和属性,从而减少相同代码的编写,子类也能重写父类的方法和属性

:o:原型链继承

function superType() {
  this.colors = [11, 22, 33]
}
function supType() {}
supType.prototype = new superType()

let instance1 = new supType()
instance1.colors.push(44)
console.log(instance1.colors) //[11, 22, 33, 44]
let instance2 = new supType()
console.log(instance2.colors) //[11, 22, 33, 44]
// 缺点:原型里的引用类型属性是共享的,实例无法做到数据独立

:o:构造函数继承

:boom:疑问:用必要使用 call 方法吗,使用 call 方法调用父构造函数本质不就等于将 this.colors 定义在子构造函数上吗,如果是怕引用属性被共享,把引用属性定义在子构造函数上不就行了。答:说了是要继承,写自己身上还叫继承吗,是从继承的角度出发的,为了能继承父构造函数身上的引用属性

function superType() {
  this.colors = [11, 22, 33]
}
function supType() {
  superType.call(this)
  this.name = 'supType'
}
let instance1 = new supType()
console.log(instance1)
instance1.colors.push(44)
console.log(instance1.colors) //[11, 22, 33, 44]
let instance2 = new supType()
console.log(instance2.colors) //[11, 22, 33]
// 缺点:无法访问父类原型上的属性和方法

:o:组合继承

function superType() {
  this.colors = [11, 22, 33]
}
superType.prototype.sayName = function () {
  console.log('我是superType上的方法')
}
function supType() {
  superType.call(this) //第二次调用
  this.name = 'supType'
}
supType.prototype = new superType() //第一次调用
supType.prototype.constructor = supType
let instance1 = new supType()
instance1.colors.push(44)
console.log(instance1.colors) //[11, 22, 33, 44]
let instance2 = new supType()
console.log(instance2.colors) //[11, 22, 33]
// 缺点是调用了两次superType

:o:原型式继承

// Object.create(obj)会创建一个新对象,并将obj赋值给这个新对象的原型,然后返回这个新对象
let ParentObj = {
  name: 'parent',
  friends: ['p1', 'p2'],
}
let child1 = Object.create(ParentObj)
let child2 = Object.create(ParentObj)
child1.name = 'child1'
child1.friends.push('p3')
console.log(child1.name) //child1
console.log(child2.name) //parent
console.log(child2.friends) //['p1', 'p2', 'p3']
// 缺点:引用类型属性会共享,适用于不需要单独创建构造函数的业务

:o:寄生式继承

let ParentObj = {
  name: 'parent',
  friends: ['p1', 'p2'],
}
function biger(obj) {
  let newObj = Object.create(obj)
  newObj.say = function () {
    console.log(this.name)
  }
  return newObj
}
let child1 = biger(ParentObj)
let child2 = biger(ParentObj)
child1.name = 'child1'
child1.friends.push('p3')
console.log(child1.name) //child1
console.log(child2.friends) //['p1', 'p2', 'p3']
child2.say() //parent
//缺点:和原型式继承一样,只不过增强了一下,可以添加写方法和属性,还是会共享引用类型数据

:o:寄生组合式继承

function Parent(name) {
  //父类上用来存放引用属性,不会被共享
    this.name = name
    this.colors = ['11', '22', '33']
}
function Child(name, age) {
  this.age = age
  Parent.call(this, name)
}
function clone(parent, child) {
    child.prototype = Object.create(parent.prototype)
    child.prototype.constructor = child
}
clone(Parent, Child)

// 下面测试
// 公共方法变量添加到Child.prototype上
Parent.prototype.sayName = function () {
    console.log('我是共有方法')
}

let lisi = new Child('lisi', 23)
let zhangsan = new Child('zhangsan', 22)

lisi.colors.push('44')
zhangsan.sayName() //zhangsan
lisi.sayName() //lisi
console.log(lisi.colors) //['11', '22', '33', '44']
console.log(zhangsan.colors) //['11', '22', '33']

:boom:原型上用来存放公共属性和方法属性,父类上用来存放引用类型私有数据,最优继承,弥补了组合继承调用两次父函数的缺点

:o:class 继承

:boom:class 继承实际上就是上面寄生组合式继承的语法糖,

//ES6中class的继承
class Phone {
  //构造方法 如果不写,默认为一个空函数
  constructor(brand, price) {
    this.brand = brand
    this.price = price
  }
  //相当原型上的方法
  call() {
    console.log('我可以打电话!!')
  }
}

class SmartPhone extends Phone {
  //构造方法
  constructor(brand, price, color, size) {
    //注意:继承父类构造函数中的属性 (相当于Phone.call(this, brand, price)),如果有继承父类就一定要调用super(),
    //且this要在它后面使用,不然报错
    super(brand, price)
    this.color = color
    this.size = size
  }
  //定义在类的原型上
  playGame() {
    console.log('玩游戏')
  }
  call() {
    console.log('我重写了父类上的call方法')
  }
  //定义在类本身上
  static call() {
    console.log('我可以进行视频通话')
  }
}
const xiaomi = new SmartPhone('小米', 799, '黑色', '4.7inch')

console.log(xiaomi.brand) //小米
xiaomi.call() //我重写了父类上的call方法
SmartPhone.call() //我可以进行视频通话
xiaomi.playGame() //玩游戏

:o:总结

  1. 原型链继承,缺点:原型里的引用类型属性是共享的,实例无法做到数据独立
  2. 构造函数继承,缺点:无法访问父类原型上的属性和方法
  3. 组合继承,缺点:调用两次父构造函数
  4. 原型式继承,缺点:引用属性会被实例共享
  5. 寄生式继承,缺点:原型式继承的加强版,能够添加一些公共方法和属性,同样会共享引用属性
  6. 寄生组合式继承,最优继承,原型上用来存放公共属性和方法属性,父类上用来存放柜引用类型数据,弥补了组合继承的缺点
  7. class 继承,本质为寄生组合式继承的语法糖

DOM 对象

:o:查找DOM节点

:boom:Document和Element类型是实例都新增了querySelector()和querySelectorAll()两个方法

示例:

document.querySelector('#box .button') //选取id为box元素的类名为button的元素

// 表示查找在parentElement这个父节点下的所有h1,h3,h4的元素并返回
parentElement(父节点).querySelectorAll(['h1','h3','h4'])//返回的是NodeList对象

// 如果元素拥有子节点,则返回 true,否则 false。
element.hasChildNodes() 

:o:增加DOM节点

let Ul = document.createElement('ul')
let Li = document.createElement('li')

//向元素添加新的子节点,作为最后一个子节点。
Ul.appendChild(Li)	

// 在某一节点前插入一节点
父节点.insertBefore(新节点,老节点)

:o:删除DOM节点

// 从元素中移除子节点(不方便)
父节点.removeChild(子节点)	

// 更常用,无需知道父节点
子节点.parentNode.removeChild(子节点)

:o:修改DOM节点

// 替换节点
父节点.replaceChild(新节点,老节点)

:o:DOM 的属性和方法

:boom:常见属性之读取和设置

  1. element.style.样式名(驼峰命名法) 只能设置或获取元素的内联样式,不能获取css样式表里的样式,获取时如果内联样式没有设置,返回空 ''。

  2. element.currentStyle.样式名(只读) 可以读取当前元素正在生效的样式(只有IE浏览器支持....)

:boom:常见方法值读取和设置

  1. window.getComputedStyle(element,null)(只读) 第二个参数一般传一个伪元素,一般传null,该方法返回所传入元素的样式对象,可以从返回的对象中获取该元素的当前生效样式(处于window上,浏览器中直接调用,除了IE8以下的浏览器,其他所有浏览器都支持)

:boom:自定义兼容所有浏览器的获取属性方法

function GetStyle(element,attrName){
  // 此处的window很关键,不加上window,getComputerStyle就是一个变量,在ie8浏览器中顺着作用域找不到就会报错,不会往下执行,加上window就成window的一个属性,找不到之后返回undefined
  window.getComputedStyle ? getComputedStyle(element,null)[attrName] : element.currentStyle[attrName]
}

  1. document.body.style.setProperty('background-color', '#fff')
document.getElementById("id").style.backgroundColor="值" //注意驼峰名
  1. element.getAttribute() 返回元素节点的指定属性值。
  2. element.setAttribute() 把指定属性设置或更改为指定值。
var root = document.querySelector(':root');//即HTML元素

btnColorRed.addEventListener('click', function() {
    root.setAttribute('style', '--color: #e74c3c');
});

:boom:element.classList.add('value') 添加类

:boom: element.classList.remove('value') 删除某个类

:boom: element.classList.contains('value') 检查是否含有某个类

:boom:element.classList.toggle('value') 如果存在该类则删除,不存在则添加

:boom:element.classList.replace('oldValue',newValue) 替换掉之前的类名

  1. element.clientWidth 返回元素的可见宽度(只包括 content + padding)
  2. element.offsetWidth 返回元素的宽度。包括 content + padding + border
  3. element.offsetLeft 返回元素相对父元素的水平偏移位置。
  4. element.scrollWidth 返回元素的整体宽度。scrollHeight: 因为子元素比父元素高,父元素不想被子元素撑的一样高就显示出了滚动条,在滚动的过程中本元素有部分被隐藏了,scrollHeight 代表包括当前不可见部分的元素的高度。而可见部分的高度其实就是 clientHeight,也就是 scrollHeight>=clientHeight 恒成立。在有滚动条时讨论 scrollHeight 才有意义,在没有滚动条时 scrollHeight==clientHeight 恒成立。单位 px,只读元素。
  5. element.scrollLeft 返回元素左边缘与视图之间的距离。
  6. element.childNodes 返回元素子节点的 NodeList。
  7. element.className 设置或返回元素的 class 属性。
  8. element.firstChild 返回元素的首个子。
  9. element.parentNode 返回元素的父节点。
  10. element.hasChildNodes() 如果元素拥有子节点,则返回 true,否则 false。
  11. element.id 设置或返回元素的 id。
  12. element.innerHTML 设置或返回元素的内容。
  13. element.toString() 把元素转换为字符串。

DOM 事件

:o:监听事件

// 监听事件 第三个参数为可选值,值为Boolean值,true表示开启捕获传播,false表示开启冒泡传播,不传默认开启冒泡传播
document.getElementById('myP').addEventListener('click', myFunction, true)
// 解绑事件
element.removeEventListener('mousemove', myFunction)

详情参考

:o:常用事件

  1. change 当 form 元素的内容、选择的内容或选中的状态发生改变时,发生此事件 Event
  2. load 在对象已加载时,发生此事件。 UiEventEvent
  3. resize 调整文档视图的大小时发生此事件。 UiEventEvent
  4. scroll 滚动元素的滚动条时发生此事件。 UiEventEvent
  5. reset 重置表单时发生此事件。 Event
  6. hashchange 当 URL 的锚部分发生改变时,发生此事件。 HashChangeEvent
  7. keyup 当用户松开键时,发生此事件。 KeyboardEvent
  8. keydown 当用户正在按下键时,发生此事件。 KeyboardEvent
  9. focus 在元素获得焦点时发生此事件。 FocusEvent
  10. blur 当元素失去焦点时发生此事件。 FocusEvent
  11. copy 当用户复制元素的内容时发生此事件。 ClipboardEvent
  12. cut 当用户剪切元素的内容时发生此事件。 ClipboardEvent
  13. mousedown 当用户在元素上按下鼠标按钮时,发生此事件。 MouseEvent
  14. mouseup 当用户在元素上释放鼠标按钮时,发生此事件。 MouseEvent
  15. click 当用户单击元素时发生此事件。 MouseEvent
  16. mouseenter 当指针移动到元素上时,发生此事件。 MouseEvent
  17. mouseleave 当指针从元素上移出时,发生此事件。 MouseEvent
  18. mousemove 当指针在元素上方移动时,发生此事件。 MouseEvent
  19. mouseout 当用户将鼠标指针移出元素或其中的子元素时,发生此事件。 MouseEvent
  20. mouseover 当指针移动到元素或其中的子元素上时,发生此事件。 MouseEvent
  21. wheel 当鼠标移动到元素上并滚轮滚动时触发 wheel 事件。 WheelEvent
// 示例
document.getElementById('myDIV').addEventListener('wheel', myFunction)

function myFunction(event) {
  this.style.fontSize = '35px'
}

:o:event对象的属性和方法

  1. target 返回触发事件的元素。 Event
  2. clientX 返回触发鼠标事件时,鼠标指针相对于当前窗口的水平坐标。 MouseEvent、TouchEvent
  3. offsetX 返回鼠标指针相对于目标元素边缘位置的水平坐标。 MouseEvent
  4. pageX 返回触发鼠标事件时鼠标指针相对于文档的水平坐标。 MouseEvent
  5. screenX 返回窗口/鼠标指针相对于屏幕的水平坐标。 MouseEvent
function myFunction(event) {
  var x = event.clientX // 获取水平坐标
  var y = event.clientY // 获取垂直坐标
  var coor = 'X coords: ' + x + ', Y coords: ' + y
}

BOM事件

:boom:常用的一些对象和事件

window.location 对象在编写时可不使用 window 这个前缀。

:o:location

location.hostname 返回 web 主机的域名

location.pathname 返回当前页面的路径和文件名

location.port 返回 web 主机的端口 (80 或 443)

location.protocol 返回所使用的 web 协议(http: 或 https:)

:o:history

history.back() - 与在浏览器点击后退按钮相同

history.forward() - 与在浏览器中点击向前按钮相同

:o:navigator

`txt = "浏览器代号: " + navigator.appCodeName;

txt+= "浏览器名称: " + navigator.appName;

txt+= "浏览器版本: " + navigator.appVersion;

txt+= "启用Cookies: " + navigator.cookieEnabled";

txt+= "硬件平台: " + navigator.platform;

txt+= "用户代理: " + navigator.userAgent";

txt+= "用户代理语言: " + navigator.lang";

document.getElementById("example").innerHTML=txt;

:boom:警告!!! 来自 navigator 对象的信息具有误导性,不应该被用于检测浏览器版本,这是因为:

navigator 数据可被浏览器使用者更改

一些浏览器对测试站点会识别错误

浏览器无法报告晚于浏览器发布的新操作系统

:boom:JavaScript弹窗

  1. window.alert("sometext");

var r=confirm("按下按钮");
if (r==true)
{
    x="你按下了\"确定\"按钮!";
}
else
{
    x="你按下了\"取消\"按钮!";
}
  1. window.prompt("sometext","defaultvalue");