JS基础
title: JS基础
date: 2019-08-19 13:39:26
tags: javascript
categories: # 这里写的分类会自动汇集到 categories 页面上,分类可以多级
- JS # 一级分类
- JS基础 # 二级分类
JS 对象
对象,就是一种无序的数据集合,由若干个“键值对”(key-value)构成。
{key: value}
是 JS 对象字面量写法
数据类型
number, boolean, string, undefined, null, object, symbol
原始类型,又叫基本类型,值类型,number, string, boolean,undefined,null
。指保存在栈内存里的简单数据段
引用类型,对象 object。又可分为狭义的对象,数组,函数,正则。指保存在堆内存中的对象
其中 js 的 number 类型是浮点类型,不是整型.并且浮点类型基于 IEEE 754 标准实现,在使用中会遇到某些 Bug。
NaN 属于 number 类型,NaN
表示Not a Number
,并且 NaN 不等于自身.
undefined
: 表示未定义或不存在.由于没有定义,所以目前没有值
null
: 表示无值.即此处的值就是”无”的状态.
typeof
typeof 对于基本类型,除了 null
都可以显示正确的类型
typeof undefined
输出 undefined
typeof []
// ‘object’
typeof {}
// ‘object’
typeof function(){}
//‘function’(这是具体的判断)
typeof null
输出 object
PS:为什么会出现这种情况呢?因为在 JS 的最初版本中,使用的是 32 位系统,为了性能考虑使用低位存储了变量的类型信息,000 开头代表是对象,然而 null 表示为全零,所以将它错误的判断为 object 。
运算符
&& (And)
|| (or)
! (not)
1 | a = 1 && 3; //3 |
instanceof
运算符,判断是否为某一种的实例.
==
和 ===
,前者先转化类型,后进行判断,后者比较类型和值
三目运算符
condition ? true case : false case
四则运算符
只有当加法运算时,其中一方是字符串类型,就会把另一个也转为字符串类型。其他运算只要其中一方是数字,那么另一方就转为数字。并且加法运算会触发三种类型转换:将值转换为原始值,转换为数字,转换为字符串。
1 | 加法拼接两个字符串 |
valueof()
返回适合该对象类型的原始值。
toString()
将该对象的原始值以字符串形式返回。
这两个方法一般是交由 js 去隐式调用。
在数值运算中,优先调用 valueof().
在字符串运算中,优先调用 toString().
自增运算符: a 或 a
1 | (a = 1), (b = a++); //a=2,b=1 |
无论是先加,还是后加,a 的值计算后一定会加 1;
1 | (a = 3), (b = 4); |
逗号运算符
取后一个表达式的值.也就是说,前一个会被覆盖.
1 | var d = ((a = 1), (b = 2)); // 2 |
boolean 值
false: undefined null 0 NaN 空字符串
除此之外,其他所有值都转化为 true,包括所有对象.
==
比较相等性
相等操作符会先转换操作数(通常称为强制转型),然后比较它们的相等性。
在转换不同的数据类型时,相等操作符遵循下列基本规则:
- 如果有一个操作数是布尔值,则在比较相等性之前,将其转换为数值;
- 如果一个操作数是字符串,另一个操作数是数值,在比较之前先将字符串转换为数值;
- 如果一个操作数是对象,另一个操作数不是,则调用对象的 valueOf() 方法,用得到的基本类型值按照前面的规则进行比较;
- 如果有一个操作数是 NaN,无论另一个操作数是什么,相等操作符都返回 false;
- 如果两个操作数都是对象,则比较它们是不是同一个对象。如果指向同一个对象,则相等操作符返回 true;
- 在比较相等性之前,不能将 null 和 undefined 转成其他值。
- null 和 undefined 是相等的。
1 | [] == [] //false |
JSON 格式(javascript object notation)
- 复合类型的值只能是数组或对象,不能是函数、正则表达式对象、日期对象。
- 简单类型的值只有四种:字符串、数值(必须以十进制表示)、布尔值和 null(不能使用 NaN, Infinity, -Infinity 和 undefined)。
- 字符串必须使用双引号表示,不能使用单引号。
- 对象的键名必须放在双引号里面。
- 数组或对象最后一个成员的后面,不能加逗号。
JSON.stringify
用于将一个值转为字符串。该字符串符合 JSON 格式,并且可以被 JSON.parse 方法还原。
1 | JSON.stringify("abc"); // ""abc"" |
上面代码将各种类型的值,转成 JSON 字符串。
需要注意的是,对于原始类型的字符串,转换结果会带双引号。
如果原始对象中,有一个成员的值是undefined
、函数或 XML 对象,这个成员会被过滤。
如果数组的成员是undefined
、函数或 XML 对象,则这些值被转成null
。
正则对象会被转成空对象。
JSON.parse()
用于将 JSON 字符串转化成对象。
1 | JSON.parse("{}"); // {} |
JSON.parse 只能解析字符串,下面这种情况会报错.
1 | var str = { name: "张三" }; |
JavaScript 对象的字面量写法只是长的像 JSON 格式数据,二者属于不同的范畴,JavaScript 对象中很多类型(函数、正则、Date) JSON 格式的规范并不支持,JavaScript 对象的字面量写法更宽松。
写 JavaScript 的基本规范
1.不要在同一行声明多个变量。
2.请使用 ===/!==来比较 true/false 或者数值
3.使用对象字面量替代 new Array 这种形式
4.不要使用全局函数。
5.Switch 语句必须带有 default 分支
6.函数不应该有时候有返回值,有时候没有返回值。
7.For 循环必须使用大括号
8.If 语句必须使用大括号
9.for-in 循环中的变量 应该使用 var 关键字明确限定作用域,从而避免作用域污染。
JS 函数
函数会声明前置
1 | foo(); //hello |
函数表达式不会
1 | fn()//报错 |
1 | var str = "一号"; |
输出
1 | //考察变量声明和作用域 |
js 对象查看所有属性
1 | var obj = { p: 1 }; |
删除命令
delete object.p
循环判断
1 | switch(判断语句){ |
while 循环
1 | while (expression) { |
do-while 循环
1 | do { |
for 循环
1 | for (var i = 0; i < 5; i++) { |
执行步骤如下:
初始化语句: var i=0
,只执行第一次
判断语句: i<5
条件改变语句: i++
内部执行: console.log(i)
先执行初始化语句,然后判断条件是否成立,成立就执行内部语句,最后改变条件.重复进行判断.
for-in 循环
1 | var arr = [1, 2, 3]; |
break
退出本次循环
continue
跳过本次循环执行下次循环
引用类型
1 | //a,b存放在栈内存中 |
1 | var obj3 = { name: "hello" }; |
1 | function sum() { |
函数的参数传递
1 | function inc(n) { |
1 | function incObj(obj) { |
深拷贝
1 | var obj = { |
缺点: JSON 不支持函数、引用、undefined、RegExp、Date……
jQuery.extend()
jQuery.extend( [deep], target, object1 [, objectN ] )
其中 deep 为 Boolean 类型,如果是true
,则进行深拷贝。
1 | var target = { a: 1, b: 1 }; |
递归深拷贝
1 | function clone(object) { |
函数
函数返回值 return
通过 return 返回结果,调用 return 后,函数立即中断并返回结果,即使后面还有语句也不再执行.
立即执行函数
将整个函数表达式包含在括号内,最后添加一个括号表示执行.
1 | (function () { |
命名冲突
当在同一个作用域内定义了名字相同的变量和方法的话,无论其顺序如何,变量的赋值会覆盖方法的赋值.
1 | var fn = 3; |
当函数执行有命名冲突时,函数执行时载入顺序是变量,函数,参数
1 | function fn(fn) { |
函数作用域(scope)
作用域(scope)指的是变量存在的范围。
在函数内定义的变量不能在函数之外的任何地方访问,因为变量仅仅在该函数的域的内部有定义。相对应的,一个函数可以访问定义在其范围内的任何变量和函数。
词法作用域
创建当前函数所在的作用域.
递归
一个函数可以指向并调用自身。调用自身的函数我们称之为递归函数。在某种意义上说,递归近似于循环。两者都重复执行相同的代码,并且两者都需要一个终止条件(避免无限循环或者无限递归)。
作用域链
- 函数在执行的过程中,先从自己内部找变量
- 如果找不到,再从创建当前函数所在的作用域去找, 以此往上
- 注意找的是变量的当前的状态
闭包(closure)
它可以访问外部函数的参数和变量,但是外部函数却不能使用它的参数和变量。
一般情况下使用闭包主要是为了
- 封装数据
- 暂存数据
面试官想听的版本:
什么是闭包?
由于在 JS 中,变量的作用域属于函数作用域,在函数执行后作用域就会被清理、内存也随之回收,但是由于闭包是建立在一个函数内部的子函数,由于其可访问上级作用域的原因,即使上级函数执行完,作用域也不会随之销毁,这时的子函数——也就是闭包,便拥有了访问上级作用域中的变量的权限,即使上级函数执行完后作用域内的值也不会被销毁。
闭包解决了什么问题?
由于闭包可以缓存上级作用域,那么就使得函数外部打破了“函数作用域”的束缚,可以访问函数内部的变量。以平时使用的 Ajax 成功回调为例,这里其实就是个闭包,由于上述的特性,回调就拥有了整个上级作用域的访问和操作能力,提高了极大的便利。开发者不用去写钩子函数来操作上级函数作用域内部的变量了。
闭包的应用?
闭包随处可见,一个 Ajax 请求的成功回调,一个事件绑定的回调方法,一个 setTimeout 的延时回调,或者一个函数内部返回另一个匿名函数,这些都是闭包。简而言之,无论使用何种方式对函数类型的值进行传递,当函数在别处被调用时都有闭包的身影。
1 | function car() { |
如果没有这个闭包,函数执行后,里面 speed 变量就会被清理掉。但我们声明了 fn 这个函数,并把它返回出来赋值给新的变量 speedup。因为 speedup 是全局变量,是一直存在的,故这个 fn 函数就一直存在,speed 变量也不会被清理
1 | //演化版 |
经典范例
如下代码输出多少?如果想输出 3,那如何改造代码?
1 | var fnArr = []; |
我的理解: fnArr[i]
声明的匿名函数的作用域是全局作用域,先遍历,并未执行,生成闭包(只是 for 循环,并没有执行函数,后面没有小括号),遍历结束开始执行,fnArr 结果是 10 个匿名函数f()
,全局作用域下 0-9 的fnArr[i]
已经变成 10 了
原理解析:for 循环每次执行,都把 function(){ return i} 这个函数赋值给 fnArr[i],但这个函数不执行。
因为 fnArr[3] =function(){ return i};故当我们调用 fnArr3 时,相当于 function(){ return i};这个函数立刻执行,这时 for 循环已经完成,i 已经变成了 10。故输出 10
如果要输出 3,需要如下改造
1 | var fnArr = []; |
我的理解: 从 0 开始遍历,(function(0){ fnArr[0] = function(){return i}})(0)
,
(function(1){ fnArr[1] = function(){return i}})(1)
(function(2){ fnArr[2] = function(){return i}})(2)
,
(function(3){ fnArr[3] = function(){return i}})(3)
,
传入 3,最内部的 function 找 i 的变量,一直找到上一层,返回 3.
1 | var fnArr = []; |
如下代码输出什么?
1 | for (var i = 0; i < 5; i++) { |
如何连续输出 0,1,2,3,4
1 | for (var i = 0; i < 5; i++) { |
1 | //第二种方法 |
1 | var an = function (n) { |
函数柯里化
只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数.
写一个函数 sum,实现如下调用
1 | console.log(sum(1)(2)); //3 |
字符串
1 | str.charAt(0); //获取第一个字符 |
字符串截取
1 | var str = "hello"; |
查找
1 | str.search("he"); //返回下标 |
大小写
1 | str.toUpperCase(); |
去除首尾空格
1 | str.trim(); //返回新数组,不影响原数组 |
split()方法使用指定的分隔符字符串将一个 String 对象分割成字符串数组,以将字符串分隔为子字符串,以确定每个拆分的位置。
找到分隔符后,将其从字符串中删除,并将子字符串的数组返回。如果没有找到或者省略了分隔符,则该数组包含一个由整个字符串组成的元素。如果分隔符为空字符串,则将 str 转换为字符数组。如果分隔符出现在字符串的开始或结尾,或两者都分开,分别以空字符串开头,结尾或两者开始和结束。因此,如果字符串仅由一个分隔符实例组成,则该数组由两个空字符串组成。
示例:
1 | var str = "The quick brown fox jumps over the lazy dog."; |
当字符串为空时,split()返回一个包含一个空字符串的数组,而不是一个空数组,如果字符串和分隔符都是空字符串,则返回一个空数组。
1 | var str = ""; |
数组
1 | arr.push(newelement) //末尾新增一个 |
数组扁平化
1 | const flattenDeep = (arr) => |
数组去重
双重循环去重
1 | function unique(arr){ |
new Set 去重
1 | function unique(arr) { |
如何将浮点数点左边的数每三位添加一个逗号
如 12000000.11 转化为『12,000,000.11』?
1 | function commafy(num) { |
如何实现数组的随机排序?
1 | 方法一: |
Math
四舍五入
1 | Math.round(0.5); //1 |
绝对值
1 | Math.abs(-1); //1 |
最大值
Math.max() 函数返回一组数中的最大值。
参数是value1, value2, ...
是一组数值
1 | Math.max(10, 20,30); |
1 | floor返回小于参数值的最大整数.向下取整 |
parseInt(值,进制)
1 | parseInt(string, radix); |
parseInt()可以将字符串转为数字,也可以将数字取整。向下取整。
如果第一个字符是数字会解析直到遇到非数字结束,如果第一个字符不是数字或者符号(如:+、-)就返回 NaN。
带自动净化功能;带自动截断小数功能,且取整,不四舍五入。
radix: 基数,也就是进制.介于 2-36 之间,超出都是返回 NaN.radix 不写默认是十进制.如果传入字符串是 0x 开头,默认是 16 进制.如果是 0 开头,默认 8 进制.任何数的 0 进制都是它本身.高于进制位能表示的最大值数返回 NaN.比如说 2 的一进制返回 NaN,因为一进制只能用 1 表示,2 作为一进制来表示明显不对.
几次方
1 | Math.pow(x, y); //返回 x 的 y 次幂的值。 |
平方根
1 | Math.sqrt(4); // 2 |
random
1 | Math.random(); |
Date
1 | Date.now(); //获取1970年至今的毫秒数 |
1 | new Date(); //使用Date构造函数创建一个Date实例, |
get 方法
1 | getDate(); //日 |
set 方法类似于 get 方法,是设置时间
时间格式化过滤器
Vue 版本
1 | {{date | dateFormat }} |
异步和回调
setTimeout 和 setInterval
区别: setInterval 指定某个任务每隔一段时间就执行一次,也就是无限次的定时执行.
清除定时器: clearTimeout()和 clearInterval()
运行机制
setTimeout 和 setInterval 的运行机制是,将指定的代码移出本次执行,等到下一轮 Event Loop,再检查是否到了指定时间.如果到了,就执行对应代码,不到,就等到下一轮重新判断.这也就意味着,setTimeout 指定的代码,必须等到本次执行的所有代码都执行完,才会执行.
单线程模型
异步的任务放在任务队列(callback queue)中,setTimeout 也是.必须等放在栈(stack)中的同步代码执行完,才会执行任务队列中的.
event loop(事件循环)
经典案例:
1 | for(var i=0; i<5; i++){ |
我的理解:
开始循环遍历.(同步代码)
i=0.创建一个 console.log(i).放到队列里,并不执行.
i=1.创建一个 console.log(i).放到队列里,并不执行,
直到 i=5.依次创建了 5 个 console.log(i)
因为 setTimeout 是异步,所以必须等同步执行完才开始执行异步.
同步代码 i 循环到 5,输出,有 5 个 console.log(i)在队列里嗷嗷待哺
,把 i=5 赋值进去.得到 5 个 5.
函数节流
在时间内,如果执行,就重新开始
原理:当达到了一定的时间间隔就会执行一次;可以理解为是缩减执行频率
函数节流会用在比 input, keyup 更频繁触发的事件中,如 resize, touchmove, mousemove, scroll。throttle 会强制函数以固定的速率执行。因此这个方法比较适合应用于动画相关的场景。
常用场景:
- 搜索框搜索输入。只需用户最后一次输入完,再发送请求
- 手机号、邮箱验证输入检测
- 窗口大小 Resize。只需窗口调整完成后,计算窗口大小。防止重复渲染。
简单版本:
1 | function throttle(fn, ms) { |
时间戳版本:
1 | const throttle = (fn, wait) => { |
函数防抖
原理:将若干函数调用合成为一次,并在给定时间过去之后,或者连续事件完全触发完成之后,调用一次(仅仅只会调用一次)
适合场景:
- 滚动加载,加载更多或滚到底部监听
- 谷歌搜索框,搜索联想功能
- 高频点击提交,表单重复提交
1 | function debounce(fn, ms) { |
带有立即执行选项的防抖函数
1 | // 这个是用来获取当前时间戳的 |
对于按钮防点击来说的实现:
如果函数是立即执行的,就立即调用,如果函数是延迟执行的,就缓存上下文和参数,放到延迟函数中去执行。一旦我开始一个定时器,只要我定时器还在,你每次点击我都重新计时。一旦你点累了,定时器时间到,定时器重置为 null,就可以再次点击了。
对于延时执行函数来说的实现:
清除定时器 ID,如果是延迟调用就调用函数
节流和防抖的区别
他们的区别在于防抖只会在连续的事件周期结束时执行一次,而节流会在事件周期内按间隔时间有规律的执行多次。
我的理解:
函数节流:
只要又执行函数,就退出.必须等上一次定时器任务执行完.这一段时间只执行一次.
函数防抖:
只要又执行函数,就清除定时器,重新设置定时器.只要重复就重新开始,最后肯定有一次.
DOM 元素创建和选取
DOM 是文档对象模型,是 html 的编程接口
readyState 加载状态
1 | document.location; |
1 | document.open(); //新建文档可以使用write写入 |
Element表示 html 元素
- element 的属性:
- nodeName: 元素标签名
- nodeType:元素类型
- className:类名
- id:元素 id
- children:子元素列表
- childNodes: 子元素列表(NodeList)
- firstChild: 第一个子元素
- lastChild:最后一个
- nextSibling: 下一个兄弟元素
- previousSibling: 上一个兄弟元素
- parentNode.parentElement: 父元素
查询元素
1 | document.getElementById("target"); |
创建元素
1 | document.createElement("div"); |
修改元素
1 | 在元素末尾添加元素; |
DOM 元素属性
1 | 获取属性 |
- innerText可写属性,返回元素内包含的文本内容,在多层次的时候按照元素由浅到深的顺序拼接内容
1 | <div> |
外层 div 的 innerText 返回内容是 "123456"
- innerHTML返回元素 html 结构,在写入的时候也会自动构建 DOM
样式
修改样式
1 | document.querySelector("p").style.color = "red"; |
获取样式 getComputedStyle
1 | var node = document.querySelector("p"); |
class 操作的增删改查
1 | var nodeBox = document.querySelector(".box"); |
页面宽高
1 | element.clientHeight; //包括padding,相当于IE盒模型,content-box |
如何判断一个元素是否出现在窗口视野中
滚动的距离 + 窗口的高度 = 目标元素到顶部的距离
1 | `window.scrollTop + window.innerHeight = element.offsetTop`; |
如何判断页面滚动到底部
1 | //窗口滚动的距离 + 窗口的高度 = 整个页面的高度 |
事件
DOM 事件流: 捕获阶段,目标阶段,冒泡阶段
事件绑定
1 | <input id="btnClick" type="button" value="Click here"> |
点击事件是异步的.
会存在覆盖
DOM2 事件处理(升级版)
- addEventListener //绑定事件
- removeEventListener //解绑事件
所有 DOM 节点都包含这两个方法,且都接受三个函数:
- 事件类型
- 事件处理方法
- 布尔参数,
true
表示在捕获阶段调用,false
表示在事件冒泡阶段处理,默认是冒泡阶段
1 | btnClick.addEventListener( |
不存在覆盖,可以写多个方法
属性方法
1 | preventDefault(); //取消默认事件行为 |
事件代理
事件委托(代理)就是利用冒泡的原理,把事件加到父元素或祖先元素上,触发执行效果。
使用事件代理来实现它,监听的元素应该是这些元素的父元素,当我点击父元素内的元素时,父元素都会得到响应,并分发相应的事件。 e.target
就是点击的元素。
1 | $(".container").onclick = function (e) { |
常见事件及自定义事件
1 | 鼠标事件; |
js 拖动 div
- 使用
mouse
实现
1 | <head> |
- drag 实现
1 | <head> |
自定义事件
1 | var EventCenter = { |
JS 动画
requestAnimationFrame
可以在一定时间内自动执行
1 | function move() { |
BOM
bom 指浏览器对象模型,核心是 window 对象。是浏览器的实例
window.innerHeight属性,window.innerWidth属性.这两个属性返回网页的 CSS 布局占据的浏览器窗口的高度和宽度,单位为像素。很显然,当用户放大网页的时候(比如将网页从 100%的大小放大为 200%),这两个属性会变小。
注意,这两个属性值包括滚动条的高度和宽度。
- scrollX:滚动条横向偏移
- scrollY:滚动条纵向偏移
这两个值随着滚动位置变化而变化
window.frames返回一个类似数组的对象,成员为页面内的所有框架,包括 frame 元素和 iframe 元素。
需要注意的是,window.frames 的每个成员对应的是框架内的窗口(即框架的 window 对象),获取每个框架的 DOM 树,需要使用window.frames[0].document
。
1 | var iframe = window.getElementsByTagName("iframe")[0]; |
上面代码用于获取框架页面的标题。
iframe 元素遵守同源政策,只有当父页面与框架页面来自同一个域名,两者之间才可以用脚本通信,否则只有使用 window.postMessage 方法。
在 iframe 框架内部,使用 window.parent 指向父页面。
navigator
指向一个包含浏览器相关信息的对象.
window.getComputedStyle
getComputedStyle 是一个可以获取当前元素所有最终使用的 CSS 属性值
1 | var style = window.getComputedStyle("元素", "伪类") |
URL 的编码/解码方法
JavaScript 提供四个 URL 的编码/解码方法。
- decodeURI()
- decodeURIComponent()
- encodeURI()
- encodeURIComponent()
区别
encodeURI 方法不会对下列字符编码
1 | 1. ASCII字母 |
encodeURIComponent 方法不会对下列字符编码
1 | 1. ASCII字母 |
alert(),prompt(),confirm()
alert()、prompt()、confirm()
都是浏览器用来与用户互动的方法。它们会弹出不同的对话框,要求用户做出回应。
需要注意的是,alert()、prompt()、confirm()这三个方法弹出的对话框,都是浏览器统一规定的式样,是无法定制的。