数组去重

冒泡排序

详情
// 将数组中的数从小到大排列
var arr = [1, 4, 6, 7, 9, 3, 5, 8]
// var numi = 0
// var numj = 0
for (var j = 0; j < arr.length - 1; j++) {
// numj += 1
var flag = true
for (var i = 0; i < arr.length - 1 - j; i++) {
// document.write('(' + arr[i] + ',' + arr[i + 1] + ')')
// numi += 1
// 两两比较,如果前面的大于后面的,交换位置
if (arr[i] > arr[i + 1]) {
flag = false
var temp
temp = arr[i]
arr[i] = arr[i + 1]
arr[i + 1] = temp
// document.write('交换了')
}
}
// document.write(',arr=(' + arr + ')')
// document.write('<br>')
// 如果一趟下来,一次交换都没有做,说明就已经排好序,就不需要继续比
if (flag) {
break
}
}

实现 call apply bind 方法

详情

call 和 apply 区别

call:

Function.prototype.myCall = function(context) {
if (typeof this !== 'function') {
throw new TypeError('Error')
}
context = context || window
var args = Array.prototype.slice.apply(arguments, [1]) // 获取额外参数
// var args = [...arguments].slice(1)
context.fn = this
var res = context.fn(...args)
delete context.fn
return res
}

context 为要绑定的 this,不传默认为 window
给 context 创建一个 fn 属性,并将值设置为需要调用的函数
调用 context.fn,并将额外参数 args 传递进去
删除 context 上的 fn 函数

apply:

Function.prototype.myApply = function(context) {
if (typeof this !== 'function') {
throw new TypeError('Error')
}
context = context || window
context.fn = this
let res
if (!arguments[1]) {
res = context.fn()
} else if (arguments[1].constructor.name === 'Array') {
res = context.fn(...arguments[1])
} else {
return console.error('Uncaught TypeError: CreateListFromArrayLike called on non-object')
// throw 'Uncaught TypeError: CreateListFromArrayLike called on non-object'
}
delete context.fn
return res
}

bind:

Function.prototype.myBind = function() {
var self = this // 保存原函数
var args = Array.prototype.slice.call(arguments) // 参数转为数组
// var args = [...arguments].slice(1) // 参数转为数组
var context = args.shift() // 保存需要绑定的this上下文
return function() {
// 返回一个新函数
self.apply(context, args.concat([].slice.call(arguments)))
}
}
function aaa(val, val1) {
console.log(val)
console.log(val1)
console.log(this.name)
}
aaa()
aaa.myCall({ name: '123' }, '121', 122)
aaa.myApply({ name: '123' }, ['121', 122])
aaa.myBind({ name: '123' })('111', '222')

防抖和节流

深浅拷贝

手写 Promise

手写 Promise.all

手写 Promise.race

异步调度

实现继承的方式

参考:https://www.cnblogs.com/humin/p/4556820.html

原型链继承,将父类的实例作为子类的原型,无法实现多继承

function Cat(){
}
Cat.prototype = new Animal();
Cat.prototype.name = 'cat';

构造继承,使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类(没用到原型)

function Cat(name){
Animal.call(this);
this.name = name || 'Tom';
}

组合继承,通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用

function Cat(name){
Animal.call(this);
this.name = name || 'Tom';
}
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;

调用了两次父类构造函数,生成了两份实例

寄生组合继承
通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性,避免的组合继承的缺点

es6 中的继承

class Super {}
class Sub extends Super {}

const sub = new Sub()

Sub.__proto__ === Super

子类可以直接通过 __proto__ 寻址到父类

function Super() {}
function Sub() {}

Sub.prototype = new Super()
Sub.prototype.constructor = Sub

var sub = new Sub()

Sub.__proto__ === Function.prototype

而通过 ES5 的方式,Sub.__proto__ === Function.prototype

es5继承 与 es6 继承的区别参考 https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/20

发布订阅

扁平数据转tree

实现 instanceof

// Left-hand instanceof Right-hand Right-hand.prototype 是否在 Left-hand 的原型链中
function myInstanceof(left, right) {
if (typeof left === 'undefined' || left === null) return false

// getProtypeOf是Object对象自带的一个方法,能够拿到参数的原型对象
let proto = Object.getPrototypeOf(left)
while (true) {
// 查找到尽头,还没找到
if (proto == null) return false
// 找到相同的原型对象
if (proto == right.prototype) return true
proto = Object.getPrototypeof(proto)
}
}

实现一个 new 操作符

实现 JSON.stringify

手机号 3-4-4 分割
千分位格式化数字

function Student(name) {
this.name = name
}

let stu = new Student('zs') // Student {name: 'zs'}

new 做了哪些事

  1. 创建一个新的空对象,类型是 Student
  2. 将 this 指向这个新的对象
  3. 执行构造函数 目的:给这个新对象加属性和方法
  4. 返回这个新对象

简单实现

function NEW(fun) {
// 判断是否是函数
if (typeof fun !== 'function') {
throw new Error('第一个参数应该是函数')
}

// 创建一个空对象,并将原型指向 fun.prototype
// const newObj = {}
// newObj.__proto__ = fun.prototype
const newObj = Object.create(fun.prototype)

const argsArr = [].slice.call(arguments, 1)

// 将构造函数 this 指向 newObj,并执行构造函数
const result = fun.apply(newObj, argsArr)

// 如果构造函数本身有返回值,且返回值为对象时,会将本身返回值返回,如果返回值为简单类型,会忽略
const isObject = typeof result === 'object' && result !== null
const isFunction = typeof result === 'function'
if (isObject || isFunction) {
return result
}
// 返回新对象
return newObj
}
const stu = NEW(Student, 'ls')
console.log(stu) // Student {name: 'ls'}

lodash 插件 cloneDeep

浅拷贝

数组:slice()/concat()/Array.from()/扩展运算符
对象:Object.assign()/扩展运算符

深拷贝

  • 通过 JSON.parse(JSON.stringify(obj))

这种方法只能复制 JSON 格式支持的属性名和值,不支持的属性名和值会直接忽略:会忽略 undefined、symbol,不能序列化函数,不能解决循环引用的对象 参考MDN

JSON.parse(JSON.stringify({
[Symbol('a')]: 'abc',
b: function() {},
c: undefined,
d: Infinity,
e: NaN,
}))
// 返回 {d: null, e: null}
  • 实现简单深拷贝
function deepCopy(source) {
if (Array.isArray(source)) {
const target = []
for (const [index, value] of source.entries()) {
target[index] = deepCopy(value)
}
return target

// 简化 => return source.map(elem => deepCopy(elem))
} else if (typeof source === 'object' && source !== null) {
const target = {}
for (const [key, value] of Object.entries(source)) {
target[key] = deepCopy(value)
}
return target

// 简化 => return Object.fromEntries(Object.entries(source).map(([key, val]) => [key, deepCopy(val)]))
} else {
// 基础类型无需拷贝
return source
}
}

Object.fromEntries() 方法把键值对列表转换为一个对象,是 Object.entries 的反转

循环引用(环)

解决思路: 通过一个WeakMap来存储拷贝过的对象

let hash = new WeakMap()
if (hash.has(source)) {
return hash.get(source)
}

hash.set(source, target)

特殊对象的拷贝

// 拷贝 Function
target = new Function(`return ${source.toString()}`)()
// 拷贝 Date
target = new Date(source.getTime())

// 拷贝 RegExp
target = new RegExp(source)

防抖和节流都是为了解决短时间内大量触发某函数而导致的性能问题,比如触发频率过高导致的响应速度跟不上触发频率,出现延迟,假死或卡顿的现象

防抖(debounce)

在事件被触发 n 秒后再执行回调函数,如果在这 n 秒内又被触发,则重新计时(短时间内连续触发的事件 只有效执行一次)

应用场景

  • 用户在输入框中连续输入一串字符后,只会在输入完后去执行最后一次的查询请求,这样可以有效减少请求次数,节约请求资源

  • window 的 resize、scroll 事件,不断地调整浏览器的窗口大小、或者滚动时会触发对应事件,防抖让其只触发一次

节流(throttle)

规定一个单位时间 n,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内事件被触发多次,只有一次能生效(每 n 秒触发一次)

应用场景

  • 鼠标连续不断地触发某事件(如点击),n 秒内只触发一次

  • 监听滚动事件,比如是否滑到底部自动加载更多

区别

防抖的作用是将多个连续的debounced调用合并为一次callback调用。防抖是基于最近次 debounced 调用来重置 waitTime,如果debounced事件触发间隔小于 waitTimecallback就不会执行;

节流的作用是限制callback调用的频率(每waitTime调用一次)。是基于上次 callback 调用来计算 waitTime 的,不管callback 事件触发有多频繁,只要距离上次 callback 调用超过了 waitTime,就一定会进行下次 callback 调用。

– 原理:

防抖是 debounced 维护了一个计时器,规定在 waitTime 时间后触发 callback,但是在 waitTime 时间内再次触发 debounced 的话,会清除当前的 timer 然后重新计时,这样一来,只有最后一次debounced 操作才能触发 callback

节流是通过判断是否到达一定时间 (waitTime) 来再次触发 callbackfuncwaitTime 时间内不能被再次触发。

实现

throttle-debounce 插件

简单实现

// 节流
function throttle(delay, func) {
let flag = true
return function() {
const context = this
const arg = arguments
if (flag) {
flag = false
setTimeout(() => {
func.apply(context, arg)
}, delay)
}
}
}

// 防抖
function debounce(delay, func) {
let timer = null
return function() {
const context = this
const args = arguments

clearTimeout(timer)
timer = setTimeout(function() {
func.apply(context, args)
}, delay)
}
}

let 与 const

[知乎]我用了两个月的时间才理解 let

[MDN]变量提升

let 的使用

  • let 声明的变量只有在当前作用域(块作用域)有效
{
var a = 1
let b = 2
}

console.log(a) // 1
console.log(b) // ReferenceError: b is not defined
  • 不允许重复声明
var a = 1
let a = 2 // SyntaxError: Identifier 'a' has already been declared
let b = 3
const b = 4 // SyntaxError: Identifier 'b' has already been declared
  • 使用 let 声明的全局变量,不会成为 window 的属性
let c = 1
console.log(window.c) // undefined
console.log(c) // 1
  • 存在变量提升
let a = 1
{
a = 2
let a
}
// 如果 let 不会提升,那么 a = 2 就会将外面的 a 由 1 变成 2
// 但运行发现 a = 2 报错:Uncaught ReferenceError: Cannot access 'a' before initialization
a = 1; let a  // Uncaught ReferenceError: Cannot access 'a' before initialization

总结:

  • let/const 声明的「创建」过程被提升了,但是「初始化」没有提升,var 声明的「创建」和「初始化」都被提升了,但「赋值」没被提升,function 声明的「创建」、「初始化」和「赋值」都被提升了
  • let 声明会提升到块顶部,从块顶部到该变量的初始化语句,这块区域叫做 TDZ(暂时死区),所谓暂时死区,就是不能在初始化之前,使用变量
  • 如果你在 TDZ 内使用该变量,JS 就会报错

如果 let x 的初始化过程失败了,那么

  • x 变量就将永远处于 created 状态
  • 你无法再次对 x 进行初始化(初始化只有一次机会,而那次机会你失败了)
  • 由于 x 无法被初始化,所以 x 永远处在暂时死区

const 的使用

const 声明一个常量。常量:代码执行的过程中,不可以修改常量里面的值

  • const 声明的量不可以改变
const PI = 3.1415
PI = 3 // TypeError: Assignment to constant variable
  • const 声明的变量必须赋值
const num
// SyntaxError: Missing initializer in const declaration
  • 如果 const 声明了一个对象,仅仅保证地址不变,可以修改对象的属性
const obj = { name: 'zs' }
obj.age = 18 // 正确
obj = {} // TypeError: Assignment to constant variable
  • 其他用法和 let 一样

模板字符串

// 定义一个字符串
let str = `hello world`

// 内部允许换行
let str = `
hello
world
`

// 内部可以使用表达式
let str = `你好,我是${name}`

箭头函数

特点

  • 不存在 prototype 这个属性
let a = () => {}
console.log(a.prototype) // undefined
  • 没有自己的 this,arguments

箭头函数的 this、arguments 都是在定义函数时绑定外层的 this 和 arguments,而不是在执行过程中绑定的,所以不会因为调用者不同而发生变化。
可以使用剩余参数(Rest 参数)表示法获得的自身入参列表

因为箭头函数没有 this,因此箭头函数不能作为构造函数

不能用 call()、apply()、bind() 这些方法改变 this 的指向

fn = function(){
let arrow = (...args) => {
console.log(arguments) // 外层的入参列表 -> Arguments(3) [1, 2, 3, callee: ƒ, Symbol(Symbol.iterator): ƒ]
console.log(args) // 使用剩余参数表示法获得的自身入参列表 -> (3) [4, 5, 6]
}
arrow(4, 5, 6)
console.log(arrow.length) // 0
}
fn(1, 2, 3)
  • 如果函数体只有一行语句,并且需要返回这个值,那么可以省略 {} 和 return
let fn = (n1, n2) => n1 + n2
  • Rest 参数和 arguments 对象的区别:

rest 参数只包括那些没有给出名称的参数,arguments 包含所有参数

rest 参数之后不能再有其他参数,否则会报错

函数的 length 属性,不包括 rest 参数

arguments 对象不是真正的数组,而 rest 参数是数组实例,可以直接使用数组的方法

对象简化语法

// 当属性的 key 和变量的名相同时可以简写
let person = { name: name } ==> let person = { name }

// 声明函数
let cal = {
add: function () {
return 1
},
// 可以省略 `:function`
add(){
return 1
}
}

属性名表达式

  • ES6 允许字面量定义对象时,用表达式作为对象的属性名,即把表达式放在方括号内。
let propKey = 'foo'
let methodKey = 'bar'

let obj = {
[propKey]: true, // foo: true
['a' + 'bc']: 123, // abc: 123
[methodKey]() {
return 'hi'
}
}

class 关键字

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Classes

ES5 中通过 构造函数 + 原型 的方式来实现面向对象

// 构造函数
function Person() {
this.name = 'jack'
this.age = 18
}

// 在原型中添加实例方法
Person.prototype.say = function() {
console.log(this.name, this.age)
}

// 创建实例
const p = new Person()
p.say()

ES6 中出现了 class 关键字,用来实现面向对象

class 声明不允许再次声明已经存在的类,否则将会抛出一个类型错误
class 声明不可以提升
class 仅仅是一个语法结构(语法糖),本质还是函数,实现继承本质上还是通过构造函数 + 原型的方式

class Person {}
Person instanceof Function // true
Person.prototype.constructor === Person // true

类声明

// 创建 Person 类
class Person {
// 类的构造函数 constructor 固定名称
constructor(name, age) {
this.name = name
this.age = age
}

// 添加实例方法
say() {
console.log(this.name, this.age)
}
}

// 创建实例
const p = new Person('tom', 18)
console.log(p) // Person {name: 'tom', age: 18}
p.say() // tom 18

类的内部所有定义的方法,都是不可枚举的

Object.keys(Person.prototype) // []
Object.getOwnPropertyNames(Person.prototype) //

类表达式

赋予一个命名类表达式的名称是类的主体的本地名称

// 匿名类
let Person = class {}
new Person() // Person {}

// 命名类
let Person = class A {}
new Person() // A {}
new A() // Uncaught ReferenceError: A is not defined
console.log(Person) // class A {}
console.log(A) // Uncaught ReferenceError: A is not defined

类表达式也不存在提升

static 关键字用来定义一个类的静态方法。调用静态方法不需要实例化该类,但不能通过一个类实例调用静态方法

class Point {
constructor(x, y) {
this.x = x
this.y = y
}

static distance(a, b) {
const dx = a.x - b.x
const dy = a.y - b.y

return Math.hypot(dx, dy)
}
}

const p1 = new Point(5, 5)
const p2 = new Point(10, 10)

console.log(Point.distance(p1, p2))

继承:要实现至少需要两个 class(子类 和 父类),子类继承自父类,继承后,子类就可以使用父类中的属性或方法

// 继承

// 父类
class Person {
constructor(name, age) {
this.name = name
}

say() {
console.log('父类中的 say 方法', this.name)
}
}

// 子类
class Chinese extends Person {
constructor(name, age) {
// 子类中使用 constructor 必须手动调用 super
// super 表示父类的构造函数
// 先调用 super() 再使用 this
super()
this.name = name
this.age = age
}
}

// 创建实例
const c = new Chinese('zs', 18)
console.log(c)
c.say() // 父类中的方法

静态方法 static

静态方法不会被实例继承,而是直接通过类来调用

class Person {
static play() {
return 'play'
}
}

Person.play() // 'play'

var person = new Person()
person.play() // err person.play is not a function

静态方法可以与非静态方法重名

class Person {
static play() {
return 'static play'
}
play() {
return 'play'
}
}


Person.play() // 'static play'

var person = new Person()
person.play() // 'play'

父类的静态方法,可以被子类继承, 静态方法也是可以从super对象上调用

class Person {
static play() {
return 'static play'
}
}

class Child extends Person {
static childPlay() {
return 'Child ' + super.play()
}
}

Child.play() // 'static play'
Child.childPlay() // 'Child static play'

解构赋值

ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)

// 1. 对象解构
var { a, b } = { a: 10, b: 20 }

// 同
;({ a, b } = { a: 10, b: 20 }) // 使用没有声明的赋值,数组解构类似
console.log(a, b) // 10 20

// 提取变量并赋值
var { a: p, b: q } = { a: 10, b: 20 }
console.log(p, q) // 10 20

// 将剩余属性赋值给一个变量
var { a, b, ...rest } = { a: 10, b: 20, c: 30, d: 40 }
console.log(a, b, rest) // 10 20 {c: 30, d: 40}

// 提供默认值
var { a = 1, b = 1 } = { a: 10 }
console.log(a, b) // 10 1

// 赋值并提供默认值
var { a: aa = 10, b: bb = 1 } = { a: 10 }
console.log(aa, bb) // 10 1

// 2. 数组解构
var [a, b] = [1, 2]
console.log(a, b) // 1 2

// 将剩余数组赋值给一个变量
var [a, b, ...rest] = [1, 2, 3, 4]
console.log(a, b, rest) // 1 2 [3, 4]
// ==> var a = arr[0]; var b = arr[1]

// 提供默认值
var [c = 2, d = 2] = [10]
console.log(c, d) // 10 2

// 忽略某些值
var [a = 2, , b = 2] = [10, 20, 30]
console.log(a, b) // 10 30

// 3. 函数参数的解构赋值
function foo({ x }) {
console.log(x) // 1
}
foo({ x: 1, y: 2 })

// 函数参数默认值
function foo({ x = 10 }) {
console.log(x) // 10
}
foo()

// 4. 解构的特殊应用
// 交换变量
var a = 1
var b = 3
;[a, b] = [b, a]
console.log(a) // 3
console.log(b) // 1

// 字符串解构
var str = 'love'
var [a, b, c, d] = str
console.log(a, b, c, d) // l o v e

扩展运算符

扩展运算符(spread)是三个点(…)。作用:将一个数组转为用逗号分隔的参数序列

var arr = ['a', 'b', 'c']
console.log(...arr) // a b c

应用

// 数组深拷贝
var arr = [1, 2, 3]
var arr1 = [...arr]
console.log(arr === arr1) // false, 说明arr1和arr指向不同数组

// 把一个数组插入另一个数组字面量
var arr2 = [...arr, 4, 5, 6]
console.log(arr2) // [1, 2, 3, 4, 5, 6]

// 字符串转数组
var str = 'love'
var arr3 = [...str]
console.log(arr3) // [ 'l', 'o', 'v', 'e' ]

对象展开

let defaults = { name: 'zs', age: 18 }
let search = { ...defaults, age: 12 } // { name: 'zs', age: 12 } 后面的属性会覆盖前面的属性

对象展开仅包含对象自身的可枚举属性

class C {
p = 12
m() {}
}
let c = new C()
let clone = { ...c }
clone.p // ok
clone.m() // error!

ES6 模块化

闭包(closure)的概念

闭包是函数和声明该函数的词法环境的组合

在 js 中,在函数中可以(嵌套)定义另一个函数时,如果内部的函数引用了外部的函数的变量,产生闭包

产生闭包的条件:有两个函数,是嵌套关系,内部函数引用了外部函数的变量

闭包的作用:

  • 私有变量,保护数据安全
  • 持久化数据
// 闭包的基本模型
function outer() {
var num = 10
function inner () {
num++
console.log(num)
}
return inner // 把inner函数给返回出去,让外部能够调用inner函数
}

// 并不一定是有返回函数才算是产生了闭包
var f3
function f1() {
var a = 2
f3 = function() {
console.log(a)
}
}
f1()
f3() // 2

闭包的应用

计数器

需求:统计一个函数的调用次数

var count = 0
function fn() {
count++
console.log('我被调用了,调用次数是' + count)
}
fn()
fn()
fn()
// 缺点:count是全局变量,不安全

使用闭包解决这个问题

function outer() {
var count = 0 // 私有变量, 将 count 保护起来了
function add() {
count++
console.log('当前count' + count)
}
return add
}
var result = outer()
result()

缓存的私有化

计算斐波那契数列,会有很大的性能问题,因为重复的计算了很多次,因此我们可以使用缓存来解决这个性能问题。

缺点:既然使用缓存,就需要保证缓存的数据的安全,不能被别人修改,因此,需要使用闭包来实现缓存的私有化。

function outer() {
// 缓存
var arr = []

var fbi = function(n) {
if (n == 1 || n == 2) {
return 1
}
if (arr[n]) {
return arr[n]
} else {
var temp = fbi(n - 1) + fbi(n - 2)
arr[n] = temp //存入缓存
return temp
}
}
return fbi
}
var fbi = outer()
console.log(fbi(40))

闭包存在的问题

正常情况下:函数在调用的时候,去开辟一块内存空间用来执行内部的代码,当函数调用结束的时候,要销毁开辟的空间,节省内存
闭包占用的内存是不会被释放的,因此,如果滥用闭包,会造成内存泄漏的问题。闭包很强大,但是只有在必须使用闭包的时候才使用

js 的垃圾回收机制(了解)

  • 内存:计算机中所有程序的运行都是在内存 中进行的,因此内存的性能对计算机的影响非常大,运行程序需要消耗内存,当程序结束时,内存会得到释放。
  • javascript 分配内存:当我们定义变量,javascript 自动分配内存存储数据。无论是值类型或者是引用类型,都需要存储在内存中。
  • 垃圾回收:当代码执行结束,分配的内存已经不需要了,这时候需要将内存进行回收,在 javascript 语言中,垃圾回收机器会帮我们回收不再需要使用的内存。
引用记数法清除

引用记数垃圾收集:如果没有引用指向某个对象(或者是函数作用域),那么这个对象或者函数作用域就会被垃圾回收机制回收。

var o = {
name: 'zs'
}
// 对象被 o 变量引用,引用记数 1
var obj = o // 变量被 o 和 obj 引用,引用记数 2
o = 1 // o 不在引用对象了,引用记数 1
obj = null // obj 不在引用对象了,引用记数 0,可以被垃圾回收了

引用计数法无法解决循环引用导致的内存泄露

function fn() {
var obj1 = {} // 引用计数为 2
var obj2 = {} // 引用计数为 2
obj1.a = obj2
obj2.b = obj1
}
fn() // 销毁fn调用开辟的空间, 但是由于引用计数考虑到两个对象都不是零引用的对象,就不能够被垃圾回收机制给回收掉
标记清除法清除

使用引用计数法进行垃圾回收的时候,会出现循环引用导致内存泄漏的问题。因此现代的浏览器都采用标记清除法来进行垃圾回收。

这个算法假定设置一个叫做根(root)的对象(在 Javascript 里,根是全局对象 Window)。定期的,垃圾回收器将从根开始,找所有从根开始引用的对象,然后找这些对象引用的对象……从根开始,垃圾回收器将找到所有可以获得的对象和所有不能获得的对象。

从 2012 年起,所有现代浏览器都使用了标记 - 清除垃圾回收算法。

闭包占用内存释放

当闭包的功能不在需要使用了,将这个变量指向 null, 这样闭包占用的内存就可以被回收掉了

function outer() {
var count = 0
function fn() {
count++
console.log('执行次数' + count)
}
return fn
}
var result = outer()
result()
result = null // 当函数 fn 没有被变量引用了,那么函数 fn 就会被回收,函数 fn 一旦被回收,那么 outer调用形成的作用域也就得到了释放

语法

parseInt() 函数解析一个字符串参数,指定该字符串为指定基数的进制值,并返回一个 10 进制的整数,如果被解析参数的第一个字符无法被转化成数值类型,则返回 NaN

参考 parseInt

parseInt(string, radix)

string 要被解析的值,如果参数不是一个字符串,则将其转换为字符串

radix 基数,表示进制,介于 2 和 36 之间的整数,参数 radix 的值为undefined、0 或没有设置该参数时,parseInt() 会根据 string 来判断数字的基数。如果输入的 string 以 “0x”或 “0x”(一个0,后面是小写或大写的X)开头,那么radix被假定为16,字符串的其余部分被解析为十六进制数。如果输入的 string以 “0”(0)开头, radix被假定为10(十进制)。如果输入的 string 以任何其他值开头, radix 是 10 (十进制)。

parseInt('123', 5) // 将 '123' 看作 5 进制数,返回十进制数 38
parseInt('4215213', 5) // 4 * 5^2 + 2 * 5^1 + 1 * 5^0 = 111 返回 111
parseInt('0x123') // 291
[1, 2, 3].map(parseInt) // [1, NaN, NaN]
// [1, 2, 3].map(parseInt(item, index))

parseFloat() 函数解析一个字符串参数并返回一个浮点数,如果给定值不能被转换成数值,则会返回 NaN

parseFloat('3.14') // 3.14
parseFloat('0x123') // 0
// [1, 2, 3].map(parseFloat) // [1, 2, 3]

问题

parseInt(0.0000005) === 5
// 如果 parseInt 第一个参数不是字符串,会将其转换成字符串
// 小于 10-6 的浮点数以指数表示
// parseint 从 float 的指数法中提取整数
String(0.0000005) // => '5e-7'
parseInt(5e-7) // => 5
parseInt('5e-7') // => 5

parseInt(1111111111111111111111) // => 1
parseInt(999999999999999999999) // => 1

parseInt((5e-7).toFixed()) // => 0

parseFloat(9999999999999999) // 10000000000000000

typeof // 只能查看基本数据类型的类型
instanceof // 判断对象的具体类型
constructor.name // 获取对象的具体类型 适用于任何类型的检测
Object.prototype.toString.call('str') // '[object String]' 适用于任何类型的检测

typeof

用于查看基本数据的数据类型, number string boolean undefined

null 比较特殊,结果是 object

如果查看复杂数据类型,返回的都是 object 类型

函数的结果是 function

// typeof 判断
// 简单类型
typeof 12 // 'number'
typeof 'abc' // 'string'
typeof true // 'boolean'
typeof undefined // 'underfined'
typeof null // 'object'

// 复杂类型 (引用类型)
typeof function() {} // 'function'
typeof [] // 'object'
typeof {} // 'object'

instanceof 判断

// 语法
object instanceof constructor

用来检测 constructor.prototype 是否存在于参数 object 的原型链中

不能用于类型识别

// instanceof 判断
var simpleStr = 'This is a simple string'
var myString = new String()
var newStr = new String('String created with constructor')
var myObj = {}
var myNonObj = Object.create(null)
var myArr = []
var myFn = function() {}

simpleStr instanceof String // 返回 false, 检查原型链会找到 undefined
myString instanceof String // 返回 true
newStr instanceof String // 返回 true
myString instanceof Object // 返回 true

myObj instanceof Object // 返回 true, 尽管原型没有定义
;({} instanceof Object) // 返回 true, 同上
myNonObj instanceof Object // 返回 false, 一种创建对象的方法,这种方法创建的对象不

myArr instanceof Array // true
myArr instanceof Object // true
myFn instanceof Object // true
myFn instanceof Function // true

constructor.name

Undefined/Null 没有 constructor 属性

var myArr = []
var myFn = function() {}
var myObj = {}
let myDate = new Date()

// 原型的构造函数
myArr.constructor.name // Array
myFn.constructor.name // Object
myObj.constructor.name // Function
myDate.constructor.name // Date

// 自定义构造函数
function Teacher(name, age) {
this.name = name
this.age = age
}
var tea = new Teacher('zs', 18)
tea.constructor.name // Teacher

Object.prototype.toString

适用于任何类型的检测,不能识别自定义对象类型

Object.prototype.toString.call('str').slice(8, -1) // String
// 正则 => RegExp
// 时间对象 => Date
// 字符串 => String
// 对象 => Object
// 数组 => Array

// 自定义构造函数
function Teacher(name, age) {
this.name = name
this.age = age
}
var tea = new Teacher('zs', 18)
Object.prototype.toString.call(tea) // '[object Object]'

原生 js 中 for 语句

可使用 continue 跳出当前循环, break 跳出整个循环

如果 for 语句在函数中,使用 return 可以结束 for 循环,同时也会结束函数后续代码的执行

for (var i = 0; i < arr.length; i++) {
console.log(arr[i])
}

原生 js 中数组的 forEach 方法

遍历数组

不能用 break continue 语句跳出整个循环
不支持 return 操作输出,return 只用于跳出当前循环

arr.forEach(function(item, index, arr) {
console.log(item)
console.log(this)
})
// 返回值: undefined

原生 js 中 for…in 语句

遍历对象

支持 break, continue 跳出循环

会枚举原型链中的属性

for (var key in obj) {
console.log(key) // 键
console.log(obj[key]) // 值
}

如果使用 for in 遍历数组,会产生一些问题

var arr = ['a', 'b', 'c']
a.name = 'd'
for (var index in arr) {
console.log(index) // '0', '1', '2', 'name'
}
  1. 数组的索引值 index 是 String 类型
  2. 会将 expando 属性也遍历出来
  3. 在某些情况下,在遍历数组元素时顺序是任意的

es6 for…of 方法

遍历类数组集合(Array, Map, Set, String, Arguments)

支持 break, continuethrow

let arr = [1, 2, 3, 4]
for (const item of arr) {
console.log(item)
}

for…of 与 for…in 的区别

参考 MDN

无论是 for…in 还是 for…of 语句都是迭代一些东西。它们之间的主要区别在于它们的迭代方式。

for…in 语句以任意顺序迭代对象的可枚举属性。

for…of 语句遍历可迭代对象定义要迭代的数据。

jquery 中的 each 方法

遍历 jQuery 对象集合,为每个匹配的元素执行一个函数

$(selector).each(function(index, element) {
// index 表示当前元素在所有匹配元素中的索引号
// element 表示当前元素 dom 对象
// this 在函数内部,this指向了element
})

$('li').each(function(index, ele) {
// $(ele).css("backgroundColor", arr[index])
$(this).css('backgroundColor', arr[index])
})

php 中 foreach 语句

用来遍历数组(关联数组和索引数组均可)。

foreach($arr as $key => $value) {
// $arr: 要遍历的数组
// $key: 键,可以是任意变量名
// $value: 值,可以是任意变量名
}
foreach($arr as $value) {

}
// 遍历关联数组
$arr = array(
"name"=>"zs",
"age"=>18,
"sex"=>20
);
foreach($arr as $k => $v) {
echo $k . "=" . $v . "<br>";
}

作用域

作用域:变量起作用的区域,也就是说:变量定义后,可以在哪个范围内使用该变量

全局作用域 :在 script 标签内,函数外的区域就是全局作用域,在全局作用内声明的变量叫做全局变量 。全局变量可以在任意地方访问。(if/while/for 语句中声明的变量也是全局变量)

函数作用域 :在函数内的区域叫做函数作用域,在函数作用域内声明的变量叫做局部变量 ,局部变量只有在当前函数内才能访问到。

自由变量:对于一个函数来说,函数内部没有声明该变量,但在函数内部有访问该变量。对于这个函数来说, 该变量就是一个自由变量。

隐式全局变量:没有使用 var 定义的变量也是全局变量,叫做隐式全局变量。

var num = 11
function fn() {
var num1 = 22
num2 = 33
num = 33
console.log(num1)
}
fn()
console.log(num)
// console.log(num1)
console.log(num2)

变量的查找规则:

  • 函数内部可以使用函数外部的变量
  • 有局部变量就用局部变量,没有局部变量就用全局变量。

函数作用域是在函数定义的时候作用域就确定下来了,和函数在哪调用无关

var num = 123
function f1() {
console.log(num)
}

function f2() {
var num = 456
f1()
}
f2() // 123

作用域链

作用域链:只要是函数,就会形成一个作用域,如果这个函数被嵌套在其他函数中,那么外部函数也有自己的作用域,这个一直往上到全局环境,就形成了一个作用域链

变量的搜索原则:从当前作用域开始查找,一直查询到全局作用域,如果存在,就返回。如果在全局中也没有找到该变量会报错

// 1.
var num = 10
fn1()
function fn1() {
console.log(num) // undefined
var num = 20
console.log(num) // 20
}
console.log(num) // 10

// 2
var num = 10
fn1()
function fn1() {
console.log(num) // 10
num = 20
console.log(num) // 20
}
console.log(num) // 20

// 3
var num = 123
function f1(num) {
console.log(num) // 456 undefined
}
function f2() {
var num = 456
f1(num)
f1()
}
f2()

// 4
var num1 = 10
var num2 = 20
function fn(num1) {
num1 = 100
num2 = 200
num3 = 300
console.log(num1) // 100
console.log(num2) // 200
console.log(num3) // 300
var num3
}
fn()
console.log(num1) // 10
console.log(num2) // 200
console.log(num3) // error

预解析

预解析过程:js 解析器在执行代码前,会把所有变量的声明和函数的声明提升到当前作用域的顶部。例如var a = 11其实会分为var aa = 11两部分,其中var a会被提升

预解析规则:

  1. var 声明的变量:只提升声明,不会提升赋值
  2. 函数声明:整体提升
  3. 先提升 var 声明的变量,后提升函数声明
  4. 遇到重名的 var 声明, var 声明会被忽略,值会保留
  5. 遇到重名的函数声明,后者会覆盖前者
  6. 如果 var 声明和函数声明同名,函数声明会把 var 声明覆盖
// 函数预解析
// 1.
function fn() {
console.log(a) // undefined
}
fn()
var a = 1

// 2.
var n = 45
function fn5() {
console.log(n) // undefined
n = 20
console.log(n) // 20
var n = 0
console.log(n) // 0
}
fn5()
console.log(n) // 45

// 3.
console.log(b) // 函数体
var b = 23
function b() {
console.log(b)
}
console.log(b) // 23
// b() // 报错

// 4.
console.log(c) // 函数体
c() // 嘿嘿
var c = function() {
comsole.log('哈哈')
}

function c() {
console.log('嘿嘿')
}

// 5.
console.log(fn1) // 函数体
fn1()
function fn1() {
console.log('哈哈') // 哈哈
}
console.log(fn2) // undefined
fn2() // 报错
var fn2 = function() {
console.log('嘿嘿')
}
// 对于函数表达式,函数的调用必须在表达式声明之后
fn2() // 嘿嘿

// 6.
// 只有用 var 声明的变量才会预解析
console.log(d) // 报错
d = 5

// 7.
console.log(e)
console.log(f) // 报错 f is not defined
var e = (f = 10)
console.log(f) // 10

// 8.
if ('a' in window) {
var a = 'abc'
}
console.log(a) // abc

不要在一个作用域内重复的声明相同的变量和函数