JS
延迟加载js
<script src="" defer> </script>
<script src="" async> </script>
defer 异步并行下载,在document解析完后执行,多脚本按顺序执行,
async 异步并行下载,只要下载完就执行,不分顺序。
输出结果
console.log(typeof NaN)
console.log(typeof undefined)
console.log(typeof null)
number , undefined obj
null 是特殊的object 类型
输出结果
console.log(true + 1)
console.log('jason' + true)
console.log(undefined + 1)
2 , jasontrue , NaN
基本上处理字符串优先转化,剩下的都是Number转化,
Number(true) = 1
Number(undefined) 结果为NaN ,NaN+什么数字都是NaN
输出结果
var bar = 1
function test() {
console.log(bar)
var bar = 2;
console.log(bar);
}
undefined , 2
在test方法里,定义的var bar = 2 被变量提升了。
输出结果
for(var i= 0; i<3; i++) {
setTimeout(function () {
console.log(i)
},1000)
}
3,3,3 间隔为1秒
因为访问的是for 里定义的var i 所有定时起
输出结果
var foo = function() {
console.log(1)
}
function foo() {
console.log(2)
}
foo()
1
声明的优先
输出结果
function c() {
var b = 1;
function a() {
console.log(b)
var b = 2
console.log(b)
}
console.log(b)
}
c()
undefined , 2 ,1
输出结果
var name = 'aaa'
(function(){
if(typeof name == 'undefined') {
var name = 'java'
consolo.log("111",name)
}else {
consolo.log("222",name)
}
})()
输出:111java
匿名函数是一个块作用域的模拟,if没有作用域,但是下面有定义了var,所以变量只能提升到匿名函数内,name为undefined
输出结果
var f = true
if( f === true) {
var a = 10;
}
function fn() {
var b = 20;
c = 30;
}
fn()
console.log(a)
console.log(c)
console.log(b)
10, 20,报错
var 没有块概念,所以a全局能拿到,
c是直接绑定在window上,所以也能拿到
b由于是定义在方法,外部无法找到,直接找不到定义,报错
输出结果
var a = {}
var b = { key : 'a'}
var c = {key:'c'}
a[b] = '111'
a[c] = '222'
console.log(a[b])
222
注意这里的a[b]里面的b 对象会被转化成字符串'[object Object]' 。
同理 a[c] 里的c 也会被转化成 '[object Object]'
所以 结果是 a['[object Object]'] = '111' ; a['[object Object]'] = '222'
所有最后等于222
js数组去除重复的方法
//方法1 new Set
function unique(array) {
let set = new Set(array)
return Array.form(set)
}
//方法2 遍历 indexOf
function unique(array) {
let lastList = []
array.foreach( (item) => {
if(lastList.indexOf(item) < 0) {
lastList.push(item)
}
})
return lastList
}
//方法3 递归删除法
function unique(array) {
array = array.sort()
var len = array.length
function loop(index) {
if(index >= 1) {
if(array[index] === array[index - 1 ]) {
array.splice(index,1)
}
loop(index - 1)
}
}
loop(len - 1)
return array
}
console.log(unique2([3,4,4,1,2,4,1,100,3]))
//方法4 两数组对比法
function unique3(array) {
array = array.sort()
let lastList = [array[0]] //初始化 就保存第一元素 然后逐一跟数组对比,有重复
for(var i = 0; i < array.length; i++) {
if(lastList[lastList.length - 1] !== array[i]) {//如果当前last里面没有则加入
lastList.push(array[i])
}
}
return lastList
}
console.log(unique3([3,4,4,1,2,4,1,100,3]))
给字符串添加addPre方法,实现 "job".addPre("good") 输出 good job
通过原型链的方法实现
String.prototype.addPre = function(str) {
return str + this
}
把嵌套的二维数组转化成一维数组
let a =
[
[4,5,6,7],
[7,8,9],
[1,2,3],
]
输出
[4,5,6,7,7,8,9,1,2,3]
//方法1
function arr2ToArr(arr) {
let newList = []
arr.forEach(item => {
newList = newList.concat(item)
})
return newList
}
console.log(arr2ToArr(a))
把嵌套的二维数组 里面最大的取出,组成新数组
let a =
[
[4,5,6,7],
[7,8,9],
[1,2,3],
]
输出
[7,9,3]
function arr2ToMaxArr(arr) {
let newList = []
arr.forEach(item => {
newList = newList.push(Math.max.apply(null,item)) //这里apply可以解构数组为动态参数
})
return newList
}
console.log(arr2ToMaxArr(a))
闭包
定义:一个函数可以调用另外一个函数的作用域中的变量
函数内还有函数,两个函数作用域相连,形成闭包
形成条件:函数嵌套,内部函数调用外部函数变量
for(var i= 0; i<3; i++) {
(function (i){
setTimeout(function () {
console.log(i)
},1000)
})(i)
}
能够正常输出 1,2,3 间隔1秒,因为形成闭包,i 变量被保留下来。
call apply bind区别
三个都是改变执行的上下文对象,可以实现继承
call和apply都是立即调用
call(obj,parm1,parm2) 第二个参数可以是 动态参数,适合解构
apply(obj,arr) 第二个参数必须是数组
bind 和call使用类似,只提前绑定,返回新函数,但不立即调用方法。
js判断变量是否为数组
//
var arr = []
arr instanceof Array
// true
var arr = []
Object.prototype.toString.call(arr)
//"[object Array]"
var arr = []
Array.prototype.isPrototypeOf(arr)
//true
var arr = []
arr.constructor.toString()
//"function Array() { [native code] }"
var arr = []
Array.isArray(arr)
//true
new操作做了什么事情
- 创建一个空对象
- 将this 指向这个对象
- 通过this给对象添加属性
- 通过this,将proto属性指向构造函数的原型对象
/*
create函数要接受不定量的参数,第一个参数是构造函数(也就是new操作符的目标函数),其余参数被构造函数使用。
new Create() 是一种js语法糖。我们可以用函数调用的方式模拟实现
*/
function create(Con,...args){
//1、创建一个空的对象
let obj = {}; // let obj = Object.create({});
//2、将空对象的原型prototype指向构造函数的原型
Object.setPrototypeOf(obj,Con.prototype); // obj.__proto__ = Con.prototype
//3、改变构造函数的上下文(this),并将剩余的参数传入
let result = Con.apply(obj,args);
//4、在构造函数有返回值的情况进行判断
return result instanceof Object?result:obj;
}
①proto和constructor属性是对象所独有的;
② prototype属性是函数所独有的。但是由于JS中函数也是一种对象,所以函数也拥有proto和constructor属性。
proto属性都是由一个对象指向一个对象,即指向它们的原型对象(也可以理解为父对象),
它的作用就是当访问一个对象的属性时,如果该对象内部不存在这个属性,那么就会去它的proto属性所指向的那个对象(可以理解为父对象)里找,如果父对象也不存在这个属性,则继续往父对象的proto属性所指向的那个对象里找,如果还没找到,则继续往上找…直到原型链顶端null,再往上找就相当于在null上取值,会报错.
由以上这种通过proto属性来连接对象直到null的一条链即为我们所谓的原型链。
prototype属性它是函数所独有的,它是从一个函数指向一个对象。它的含义是函数的原型对象,
也就是这个函数(其实所有函数都可以作为构造函数)所创建的实例的原型对象,
由此可知:f1.proto === Foo.prototype,它们两个完全一样。
作用:让该函数所实例化的对象们都可以找到公用的属性和方法。任何函数在创建的时候,其实会默认同时创建该函数的prototype对象。
constructor属性的含义就是指向该对象的构造函数,所有函数(此时看成对象了)最终的构造函数都指向Function
因为创建对象的前提是需要有constructor,而这个constructor可能是对象自己本身显式定义的或者通过proto在原型链中找到的。而单从constructor这个属性来讲,只有prototype对象才有。每个函数在创建的时候,JS会同时创建一个该函数对应的prototype对象,而函数创建的对象.proto === 该函数.prototype,该函数.prototype.constructor===该函数本身,故通过函数创建的对象即使自己没有constructor属性,它也能通过proto找到对应的constructor,所以任何对象最终都可以找到其构造函数(null如果当成对象的话,将null除外)。
原型链
当访问对象属性时候,会先在对象本身属性找,没有则去proto隐式原型找,即构造函数的prototype ,没有继续在prototype的proto找,这样一层层查找形成链式。
总结:
- 一直往上找,直到null都没有,就返回undefined
- Object.prototype.proto === null
js继承判断
- 原型链继承
- 构造函数继承
- 实例继承
- 组合继承
- 寄生组合
- es6 extends
== 和 === 区别
== 是语法糖,会转化成对应的数据类型,再比较。
=== 是完全匹配
cookie 和 session区别
cookie 是保留在客户端,而且容量大小有限制,不安全,明文可以篡改。
session保留在服务端,访问多了,会响应服务端性能。通过客户端记录ID 在cookie 实现用户登陆态记录。
sort背后的原理
js自带的排序方法。
通过 unicode编码进行排序 ,默认是从小到大排序
slice和splice区别
slice 切割数组,返回切割后的内容,但原数组不改变
splice 可以删除和插入数据,替换,并返回删除的数据,原数组也会变化。
深度拷贝和浅拷贝区别
浅拷贝: 对于对象,只是复制对象的引用,修改属性,会导致两边修改。一般用Object.assign()
深度拷贝:完全复制对象的每一个属性,每一个值,并且嵌套对象依然是完全拷贝。并且被复制对象与拷贝对象无关联。修改不影响。
通过字符串转化再转对象 JSON.stringify JSON.parse可以实现。但是如果拷贝对象包含正则表达式,函数,或者undefined等值,此方法就会出现问题
自己实现递归遍历每一个属性
ES6
1.var,let,const 区别
var
- 存在变量提升
- 可以重复定义同一个var 变量名
- var 在全局定义,会直接添加到window
let
- 不存在变量提升
- 不能重复定义一样的变量名
- 不会添加到window
- 存在块作用域
- 有暂时性死区
var temp = 0
if(true){
temp = 10
let temp ;//由于这里定义了let 所以在 if块内 let上面的代码都会变成死区
}
const
和 let 差不多,但是cost不能被修改
看看结果
function demo(){
let n = 2
if (true) {
let n = 1
}
console.log(n)//以当前块范围取值
}
demo()
//输出2
输出结果
const demo = (first,...num) => {
return [first,num]
}
demo(1,2,3,4,5)
输出:[1,[2,3,4,5]]
合并对象方法
Object.assign()
Object.assign(tragetObj,inputObj)
解构
let newObj = {...a, ...b}
遍历属性赋值
for (var key in souce) {
targetObj[key] = souceObj[key]
}
promise的几个状态
有三个状态 pending(进行中) fulfilled rejected
箭头函数和普通函数区别
箭头:
- 是匿名函数,不能做构造函数,不能用new
- 没有arguments,使用的是rest参数
rest 参数(形式为...变量名),用于获取函数的多余参数
const test = (...arg) => {
console.log(arg[0])
}
test(1,2)
- 不绑定 this 获取其所在最近上下文的this使用
- 通过call apply 方法调用函数,只需传一个参数,对this没有影响
let obj = {
name: "qianzhixiang",
func: (a,b) => {
console.log(this.name,a,b);
}
};
obj.func(1,2); // 1 2
let func = obj.func;
func(1,2); // 1 2
let func_ = func.bind(obj);
func_(1,2);// 1 2
func(1,2);// 1 2
func.call(obj,1,2);// 1 2
func.apply(obj,[1,2]);// 1 2
- 箭头函数没有原型属性
- 函数不能做generator函数,不能使用yied
foreach map filter 区别
map返回新的数组对象
foreach和map都是遍历数组
filter 返回原数组的子集合,通过返回的ture/ false 判断添加返回的数组里。