2022 JS面试题吐血整理

212 阅读40分钟

js数据类型

参考链接

概念及分类

  1. 基本数据类型

概念:基本数据类型是指存放在中的简单数据段,数据大小确定,内存空间大小可以分配,它们是直接按值存放的,所以可以直接按值访问 number、string、undefined、null、boolean、symbol、bigint

  1. 引用数据类型也叫对象数据类型,包括function,object,array,date,RegExp等可以可以使用new创建的数据,又叫对象类型,他们是存放在(heap)内存中的数据

基本数据类型详解

Number数值类型 包含整数和浮点数(浮点数数值必须包含一个小数点,且小数点后面至少有一个数字)两种值

0.1+0.2=0.3对吗?为什么?怎么解决?

计算机内部存储数据使用2进制存储,两个数字进行的数学运算,首先是将这两个数字以2进制形式,存储在计算机内部,然后在计算机内部使用两个2进制数字进行计算,最后将计算结果的2进制数字转为10进制展示出来。 由于10进制的小数在转2进制的时候,规则是小数部分乘以2,判断是否得到一个整数,如果得到整数,转换完成;如果没有得到整数,则继续乘以2判断。所以,0.1和0.2在转换2进制的时候,其实是一个无限死循环,也就是一直乘以2没有得到整数的时候,但计算机内部对于无线死循环的数据,会根据一个标准保留52位。也就是说,计算机内部在存储0.1和0.2的时候,本来就不精准,两个不精准的小数在计算后,距离精准的结果是有一定误差的。

解决

方法一:parseFloat((0.1 + 0.2).toFixed(10)) === 0.3 // true
方法二:(0.1*100+0.2*100)/100

NaN:非数字类型,属于数值型基本数据类型

特点:
1):设计任何的NaN操纵都会返回NaN
console.log('ab'/10); // NaN
2) NaN不等于自身。
console.log(NaN == NaN);// false;

判断是否是Number类型

1、isNaN:判断是否是一个非数字类型,传入的非数字类型,返回true,否则返回false 注意:传入的参数首先会被转化为数值,如果参数类型为对象类型,先调用valueOf()方法,再确定该方法返回的值是否可以转换为数值类型,如果不能,再调用toString()方法,再确定返回值 2、typeof

数值类型的转换:

Number():可以用于任何的数据类型 parseInt:提取 整数数值 paseFloat:提取浮点数值

String 字符串类型 特点: 1、字符串的单引号和双引号作用效果一样 2、字符串有length属性,可以取得字符串的长度 3、字符串的值是不可变的,所以很多的字符串的api不会改变原字符串值

字符串转换: String():适用于任何的数据类型(null -> null undefined -> undefined) toString():null和undefined没有这个功能 console.log(null.toString()); //error 报错

3、Boolean 布尔类型 该类型只有两个值:true、false

Boolean(undefined):false
Boolean(null):false
Boolean(非空对象包括空数组[]和空对象{}):true
Boolean(非0): true || Boolean(0和NaN):false
Boolean(非空包括空格字符串):true || Boolean(''):false

//类型转换:
Number(true): 1     ||   Number(false) : 0
String(true):'true'      ||   String(false):'false'

null 和undefined 区别

Null 空对象指针类型 如果定了一个对象,初始化可以为null,因为null的基本类型是Null,在if语句中默认转化为false,在和数值计算默认为0 出现场景:对象不存在 类型转换:

Booleam(null) false
Number(num) 0
String(null) 'null'
Number(null) // 0

5、Undefined 申明了变量但是没有初始化,默认为undefined,在if语句中默认转化为false, undefined:表示‘缺少值’,就是应该有一个值,但是没有定义,

undefined使用场景

以下用法是典型的出现undefined情况

(1)变量被申明,等于undefined

(2)调用函数时,应该提供的参数没有提供,该参数等于undefined

(3)对象没有赋值的属性,该属性值为undefined

(4)函数没有返回值,默认返回undefined

Boolean(undefined):  false
Number(undefined):  NaN
String(undefined):  'undefined'

SymbolES6新增的一个基本数据类型,表示唯一性

let id1 = Symbol('id');
 let id2 = Symbol('id');
 console.log(id1 == id2);  //false
Symbol属性类型不支持for...in和Object.keys()

let id = Symbol("id");
 let obj = {
  [id]:'symbol'
 };
 for(let option in obj){
     console.log(obj[option]); //空
 }

引用数据类型详解

特点:

  • 引用类型的值可以改变
  • 引用数据类型可以添加属性和方法
  • 引用数据类型的赋值是对象引用
  • 引用类型的比较是引用的比较
  • 引用类型是同时保存在栈区中和堆区中的,引用类型的存储需要在内存的栈区和堆区中共同完成,栈区保存变量标识符和指向堆内存的地址

基本数据类型和引用数据类型的区别

总结基本数据类型和引用数据类型区别

1、声明变量时内存分配不同

*原始类型:在栈中,因为占据空间是固定的,可以将他们存在较小的内存中-栈中,这样便于迅速查询变量的值

*引用类型:存在堆中,栈中存储的变量,只是用来查找堆中的引用地址。

这是因为:引用值的大小会改变,所以不能把它放在栈中,否则会降低变量查寻的速度。相反,放在变量的栈空间中的值是该对象存储在堆中的地址。地址的大小是固定的,所以把它存储在栈中对变量性能无任何负面影响

20200806164212655.png

2、不同的内存分配带来不同的访问机制

在javascript中是不允许直接访问保存在堆内存中的对象的,所以在访问一个对象时,首先得到的是这个对象在堆内存中的地址,然后再按照这个地址去获得这个对象中的值,这就是传说中的按引用访问。而原始类型的值则是可以直接访问到的。

3、复制变量时的不同

1)原始值:在将一个保存着原始值的变量复制给另一个变量时,会将原始值的副本赋值给新变量,此后这两个变量是完全独立的,他们只是拥有相同的value而已。

2)引用值:在将一个保存着对象内存地址的变量复制给另一个变量时,会把这个内存地址赋值给新变量,也就是说这两个变量都指向了堆内存中的同一个对象,他们中任何一个作出的改变都会反映在另一个身上。(这里要理解的一点就是,复制对象时并不会在堆内存中新生成一个一模一样的对象,只是多了一个保存指向这个对象指针的变量罢了)。多了一个指针

4、参数传递的不同(把实参复制给形参的过程)

首先我们应该明确一点:ECMAScript中所有函数的参数都是按值来传递的。

但是为什么涉及到原始类型与引用类型的值时仍然有区别呢?还不就是因为内存分配时的差别。

1)原始值:只是把变量里的值传递给参数,之后参数和这个变量互不影响。

2)引用值:对象变量它里面的值是这个对象在堆内存中的内存地址,这一点你要时刻铭记在心!

因此它传递的值也就是这个内存地址,这也就是为什么函数内部对这个参数的修改会体现在外部的原因了,因为它们都指向同一个对象。

Js的深拷贝与浅拷贝

拷贝(shallowCopy)只是增加了一个指针指向已存在的内存地址,

深拷贝(deepCopy)是增加了一个指针并且申请了一个新的内存,使这个增加的指针指向这个新的内存,

使用深拷贝的情况下,释放内存的时候不会因为出现浅拷贝时释放同一个内存的错误。

浅复制:仅仅是指向被复制的内存地址,如果原地址发生改变,那么浅复制出来的对象也会相应的改变。

深复制:在计算机中开辟一块新的内存地址用于存放复制的对象。

检测数据类型

1、typeof 检测一些基本的数据类型

对于`null`及数组、对象,typeof均检测出为object,不能进一步判断它们的类型。

2、A instanceof B检测当前实例是否隶属于某各类

用于检测构造函数(右边)的 `prototype` 属性是否出现在某个实例对象(左边)的原型链上。

3、constructor构造函数

constructor不能判断undefinednull,并且使用它是不安全的,因为contructor的指向是可以改变的

4、hasOwnporperty 检测当前属性是否为对象的私有属性

5、is Array 判断是否为数组

6、valueOf

7、Object.portotype.toString (最好的)

判断一个值是不是NaN

1、采用内置方法 isNaN:NaN 不是一个数字且数据类型为 number,而且不等于自身

function isNaN(n) {
if (n !== n) {
return true;
} else {
return false;
}}

2、利用 NaN 是唯一一个不等于任何自身的特点

var a=NaN;
a==a; //false

3、object.is 方法

console.log(Object.is("a", NaN));
console.log(Object.is(1, NaN));
console.log(Object.is(NaN, NaN));

ajax用法

ajax用法

一、基础知识

1、ajax

通过在后台与服务器进行少量数据交换,AJAX 可以使网页实现异步更新。这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新。

2、ajax的核心步骤:

创建XMLHttpRequest对象 关于XHR: 可以异步和同步发送和接受数据,可以在请求中添加头部信息,并读取响应报文所有头信息,以及响应文本 可以监听readyState,确定响应信息正在传输中 不能从外域请求数据 经GET请求的数据会被缓存起来(需要服务段添加expires头部信息) GET请求参数长度接近和超过2048个字符,才使用POST请求 发送(异步)请求 接收相应请求,并且执行回调,响应数据

image.png

image.png

3、ajax具体过程\

(1)创建XMLHttpRequest对象

var xhr = new XMLHttpRequest();

(2)给服务器发送数据,open(),一般有两种方法:get()、post()

if(xhr!=null){    
          //如果实例化成功,就调用open()方法,就开始准备向服务器发送请求
        xhr.open("post", url, true);            
        /*三个参数:第一个是发送请求的类型,POST 和GET 两种
                  第二个是url的地址,(地址也可以是静态文件,xml文件)
                  第三个,是否是异步,true是 异步。false 是同步*/
      }

get()和post()两种基本请求方式的区别:
(a)、直观的区别就是GET把参数包含在URL中,POST通过request body传递参数。
(b)、长的说:对于GET方式的请求,浏览器会把http header和data一并发送出去,服务器响应200(返回数据);而对于POST,浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok(返回数据)。
(c)、GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留。
(d)、GET请求在URL中传送的参数是有长度限制的,而POST么有。
(e)、GET比POST更不安全,因为参数直接暴露在URL上,所以不能用来传递敏感信息。
(f)、post会有浏览器提示重新提交表单的问题,get则没有

(3)设置发送的数据,用 send 发送请求

(4)注册事件(给 ajax 设置事件)

(5)获取响应并更新页面

AJAX 的优点 缺点

1)页面无刷新更新数据:

AJAX最大优点就是能在不刷新整个页面的前提下与服务器通信维护数据。这使得Web应用程序更为迅捷地响应用户交互,并避免了在网络上发送那些没有改变的信息,减少用户等待时间,带来非常好的用户体验。

2)异步与服务器通信:

AJAX使用异步方式与服务器通信,不需要打断用户的操作,具有更加迅速的响应能力。优化了Browser和Server之间的沟通,减少不必要的数据传输、时间及降低网络上数据流量。

3)前端和后端负载平衡:

AJAX可以把以前一些服务器负担的工作转嫁到客户端,利用客户端闲置的能力来处理,减轻服务器和带宽的负担,节约空间和宽带租用成本。并且减轻服务器的负担,AJAX的原则是“按需取数据”,可以最大程度的减少冗余请求和响应对服务器造成的负担,提升站点性能。

4)基于标准被广泛支持:

AJAX基于标准化的并被广泛支持的技术,不需要下载浏览器插件或者小程序,但需要客户允许JavaScript在浏览器上执行。随着Ajax的成熟,一些简化Ajax使用方法的程序库也相继问世。同样,也出现了另一种辅助程序设计的技术,为那些不支持JavaScript的用户提供替代功能。

5)界面与应用分离:

Ajax使WEB中的界面与应用分离(也可以说是数据与呈现分离),有利于分工合作、减少非技术人员对页面的修改造成的WEB应用程序错误、提高效率、也更加适用于现在的发布系统。

AJAX 的缺点

1)AJAX 干掉了Back和History功能,即对浏览器机制的破坏:

在动态更新页面的情况下,用户无法回到前一个页面状态,因为浏览器仅能记忆历史记录中的静态页面。一个被完整读入的页面与一个已经被动态修改过的页面之间的差别非常微妙;用户通常会希望单击后退按钮能够取消他们的前一次操作,但是在Ajax应用程序中,这将无法实现的 ,后退按钮是一个标准的web站点的重要功能,但是它没法和js进行很好的合作。这是Ajax所带来的一个比较严重的问题,

2)AJAX的安全问题:

AJAX技术给用户带来很好的用户体验的同时也对IT企业带来了新的安全威胁,Ajax技术就如同对企业数据建立了一个直接通道。这使得开发者在不经意间会暴露比以前更多的数据和服务器逻辑。Ajax的逻辑可以对客户端的安全扫描技术隐藏起来,允许黑客从远端服务器上建立新的攻击。还有Ajax也难以避免一些已知的安全弱点,诸如跨站点脚步攻击、SQL注入攻击和基于Credentials的安全漏洞等等

3)对搜索引擎支持较弱:

对搜索引擎的支持比较弱。如果使用不当,AJAX会增大网络数据的流量,从而降低整个系统的性能。

4)破坏程序的异常处理机制:

至少从目前看来,像Ajax.dll,Ajaxpro.dll这些Ajax框架是会破坏程序的异常机制的

5)违背URL和资源定位的初衷:

我给你一个URL地址,如果采用了Ajax技术,也许你在该URL地址下面看到的和我在这个URL地址下看到的内容是不同的。这个和资源定位的初衷是相背离的

6)AJAX不能很好支持移动设备:

一些手持设备(如手机、PDA等)现在还不能很好的支持Ajax

7)客户端过肥,太多客户端代码造成开发上的成本:

编写复杂、容易出错 ;冗余代码比较多(层层包含js文件是AJAX的通病,再加上以往的很多服务端代码现在放到了客户端);破坏了Web的原有标准。

8)如果用户禁用了JS,网站就取不到数据

由此我们可以知道Ajax 适合用在 表单驱动的交互,深层次的树的导航,普通文本输入提示和自动完成,对数据进行过滤和操纵相关数据,快速的用户与用户间的交流响应

不适合用在 搜索 ,基本的导航,替换大量的文本,部分简单的表单

闭包

啥是闭包?

闭包就是能够读取其他函数内部变量的函数。

由于在javascript中,只有函数内部的子函数才能读取局部变量,所以说,闭包可以简单理解成“定义在一个函数内部的函数“。

所以,在本质上,闭包是将函数内部和函数外部连接起来的桥梁。

闭包有3个特性

①函数嵌套函数

②函数内部可以引用函数外部的参数和变量

③参数和变量不会被垃圾回收机制回收

闭包的优点缺点

优点

  1. 保护函数内的变量安全 ,实现封装,防止变量流入其他环境发生命名冲突
  2. 在内存中维持一个变量,可以做缓存(但使用多了同时也是一项缺点,消耗内存)
  3. 匿名自执行函数可以减少内存消耗
  4. 方便调用上下文的局部变量。

缺点

(1)被引用的私有变量不能被销毁,增大了内存消耗,造成内存泄漏,解决方法是可以在使用完变量后手动为它赋值为null;

(2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。

(3)由于闭包涉及跨域访问,所以会导致性能损失,我们可以通过把跨作用域变量存储在局部变量中,然后直接访问局部变量,来减轻对执行速度的影响

本质:

外层函数嵌套一个内层函数,内层函数作为外层函数的 return 语句的返回值。外层函数是自调用函数,并且将自身的返回值(也就是内层函数本身)赋给一个变量。在 JS 执行环境上下文中调用该变量就等价于调用了内层函数,并且在内层函数中可以访问到外层函数的局部变量又并且外层函数的局部变量不会被多次声明,此时就形成了一种闭包的写法。

对页面的影响:

  使用闭包会占有内存资源,过多的使用闭包会导致内存溢出等。

如何避免闭包引起的内存泄漏

1,在退出函数之前,将不使用的局部变量赋值为null;

2,利用Jquery释放自身指定的所有事件处理程序。

闭包常见的应用场景

柯里化函数

为了避免频繁地调用具有相同参数的函数,可以将一个多参数的函数转化为一个单参数的函数,
//普通函数
function getArea(w,h){
    return w * h;
}
const area1 = getArea(10,20);
const area2 = getArea(10,30);
const area3 = getArea(10,40);

//柯里化函数
function getArea(w){
    return function(h){
        return w * h;
    }
}
const getTenArea = getArea(10);

const area1 = getTenArea(20);
const area2 = getTenArea(30);
const area3 = getTenArea(40);


通过闭包实现变量/方法的私有化

function funOne(i){
    function getTwo(){
        console.log('参数:', i)
    }
    return getTwo;
}
const fa = funOne(100); 
const fb = funOne(200); 
const fc = funOne(300); 


匿名自执行函数

var funOne = (function(){
    var num = 0;
    return function(){
        num++;
        return num;
    }
})()

console.log(funOne());   // 1
console.log(funOne());   // 2
console.log(funOne());   // 3 


缓存一些结果

外部函数定义一个变量,内部函数可以获取或修改这个变量的值,从而就延长了这个变量的生命周期
function parent(){
    let list = [];
    function son(i){
        list.push(i);
    }
    return son;
}

const fn = parent();

fn(1);
fn(2);
fn(3);


内存泄漏

什么是内存泄漏

内存泄漏是指由于疏忽或错误造成程序未能释放已经不在使用的内存,无用的内存还在占用,得不到释放和归还,比较严重的时候,无用的内存还会增加,从而导致整个系统卡顿,甚至崩溃

内存生命周期

一般按照顺序分为三个周期

1、分配期:

分配所需要的内存,在前端javascript中,内存是自动分配的

2、使用期:

使用分配到到得内存(读写),在js中读写一个变量或者对象的属性值

3、释放期:

不需要时将该内存释放和归还,js会自动释放内存,(除闭包和部分bug外), 内存泄漏也是出现在这个时期,内存没有被释放导致的

内存回收:

Javascript 具有自动垃圾回收机制(GC:Garbage Collecation),也就是说,执行环境会负责管理代码执行过程中使用的内存。其原理是:垃圾收集器会定期(周期性)找出那些不在继续使用的变量,然后释放其内存。但是这个过程不是实时的,因为其开销比较大并且GC时停止响应其他操作,所以垃圾回收器会按照固定的时间间隔周期性的执行。

垃圾内存的两种回收方式:标记清除,引用计数。

## 常见的内存泄漏

  1. 全局变量
  2. 未销毁的定时器和回调函数
  3. 闭包
  4. 注册到全局的事件
  5. 脱离 DOM 的引用

## 内存泄漏的识别方法

怎样可以观察到内存泄漏呢?

1.经验法则是,如果连续五次垃圾回收之后,内存占用一次比一次大,就有内存泄漏。这就要求实时查看内存占用。

Chrome 浏览器查看内存占用,按照以下步骤操作。

1.  打开开发者工具,选择 Timeline 面板
1.  在顶部的`Capture`字段里面勾选 Memory
1.  点击左上角的录制按钮。
1.  在页面上进行各种操作,模拟用户的使用情况。
1.  一段时间后,点击对话框的 stop 按钮,面板上就会显示这段时间的内存占用情况。

如果内存占用基本平稳,接近水平,就说明不存在内存泄漏。

2, 命令行

命令行可以使用 Node 提供的process.memoryUsage方法。

process.memoryUsage返回一个对象,包含了 Node 进程的内存占用信息。该对象包含四个字段,单位是字节,含义如下。

image.png

判断内存泄漏,以heapUsed字段为准。

-   rss(resident set size):所有内存占用,包括指令区和堆栈。
-   heapTotal:"堆"占用的内存,包括用到的和没用到的。
-   heapUsed:用到的堆的部分。
-   external: V8 引擎内部的 C++ 对象占用的内存。

javascript垃圾回收机制原理

解决内存的泄露,垃圾回收机制会定期(周期性)找出那些不再用到的内存(变量),然后释放其内存。

现在各大浏览器通常采用的垃圾回收机制有两种方法:标记清除,引用计数。

引用计数:

工作原理:跟踪记录每个值被引用的次数。

工作流程:

  1. 声明了一个变量并将一个引用类型的值赋值给这个变量,这个引用类型值的引用次数就是1。

  2. 同一个值又被赋值给另一个变量,这个引用类型值的引用次数加1.

  3. 当包含这个引用类型值的变量又被赋值成另一个值了,那么这个引用类型值的引用次数减1.

  4. 当引用次数变成0时,说明没办法访问这个值了。

  5. 当垃圾收集器下一次运行时,它就会释放引用次数是0的值所占的内存。

function test() {
    var a = {};    // a指向对象的引用次数为1
    var b = a;     // a指向对象的引用次数加1,为2
    var c = a;     // a指向对象的引用次数再加1,为3
    var b = {};    // a指向对象的引用次数减1,为2
}

缺点:循环引用的情况下无法清除变量。

解决:手工断开js对象和DOM之间的链接。赋值为null。IE9把DOM和BOM转换成真正的JS对象了,所以避免了这个问题。

标记清除:

工作原理:是当变量进入环境时,将这个变量标记为“进入环境”。当变量离开环境时,则将其标记为“离开环境”。标记“离开环境”的就回收内存。

工作流程:

  1. 垃圾回收器,在运行的时候会给存储在内存中的所有变量都加上标记。

  2. 去掉环境中的变量以及被环境中的变量引用的变量的标记。

  3. 再被加上标记的会被视为准备删除的变量。

  4. 垃圾回收器完成内存清除工作,销毁那些带标记的值并回收他们所占用的内存空间。


function test(){
	var a = 10 ;       // 被标记 ,进入环境 
	var b = 20 ;       // 被标记 ,进入环境
}
test();            // 执行完毕 之后 a、b又被标离开环境,被回收。

事件委托

基本概念

事件代理(Event Delegation),又称之为事件委托。是JavaScript中常用绑定事件的常用技巧。顾名思义,“事件代理”即是把原本需要绑定在子元素的响应事件(click、keydown......)委托给父元素,让父元素担当事件监听的职务。事件代理的原理是DOM元素的事件冒泡。

事件委托的优点

【1】可以大量节省内存占用,减少事件注册,比如在ul上代理所有li的click事件就非常棒

【2】可以实现当新增子对象时无需再次对其绑定(动态绑定事件)

局限性

像focus和blur不存在事件冒泡,不能使用事件委托。 mousemove,mouseout存在事件冒泡,但是只能通过定位去计算,性能消耗大,所以也不试用。

如何确定事件源

Event.target 谁调用谁就 是事件源,

event.target: 指的是真正触发事件的那个元素()

event.currentTarget : 指的是绑定了事件监听的元素(可以理解为触发事件元素的父级元素)

白话就是:用户的鼠标不管点击在哪个标签上 e.currentTarget始终是在所绑定的标签事件发出

事件流

事件流:描述的是从页面中事件发生的顺序。
事件流的3个阶段,即顺序:
事件传播分成三个阶段:
捕获阶段:从window对象传导到目标节点(上层传到底层)称为“捕获阶段”(capture phase),捕获阶段不会响应任何事件;
目标阶段:在目标节点上触发,称为“目标阶段”
冒泡阶段:从目标节点传导回window对象(从底层传回上层),称为“冒泡阶段”(bubbling phase)。事件代理即是利用事件冒泡的机制把里层所需要响应的事件绑定到外层;

什么是冒泡?

冒泡是从下往上执行的,比如有个子事件和父事件,点击子事件后触发父事件,浏览器是默认事件冒泡的

如何阻止冒泡?

// 在代码中添加 event.stopPropagation();

什么是默认行为

比如说一个a 标签,有个href的链接,点击之后就会默认的进行跳转;

阻止事件默认行为

event.preventDefault();

cookie、sessionStorage和localStorage,token

webstorage是本地存储,存储在客户端,包括localStorage和sessionStorage,WebStorage的目标
提供一种在cookie之外存储会话数据的路径
提供一种存储大量可以跨会话存在的数据的机制

WebStorage的优点:

存储空间更大:cookie为4KB,而WebStorage是5MB

节省网络流量:WebStorage不会传送到服务器,存储在本地的数据可以直接获取,也不会像cookie一样美词请求都会传送到服务器,所以减少了客户端和服务器端的交互,节省了网络流量 对于那种只需要在用户浏览一组页面期间保存而关闭浏览器后就可以丢弃的数据,sessionStorage会非常方便

快速显示:有的数据存储在WebStorage上,再加上浏览器本身的缓存。获取数据时可以从本地获取会比从服务器端获取快得多,所以速度更快

安全性:WebStorage不会随着HTTP header发送到服务器端,所以安全性相对于cookie来说比较高一些,不会担心截获,但是仍然存在伪造问题

WebStorage提供了一些方法,数据操作比cookie方便

setItem (key, value) —— 保存数据,以键值对的方式储存信息。
getItem (key) —— 获取数据,将键值传入,即可获取到对应的value值。
removeItem (key) —— 删除单个数据,根据键值移除对应的信息。
clear () —— 删除所有的数据
key (index) —— 获取某个索引的key

1.生命周期比较,数据有效期不同

cookie:可以设置失效时间;如果没有设置失效时间,浏览器关闭后数据会失效

localStorage:数据会永久保存,除非手动清除

sessionStorage:会话当前数据有效就是在浏览器窗口关闭之前有效,浏览器关闭后数据会被清除

2.存放数据大小比较存储大小限制不同

cookie:传递少量数据,4KB左右。每次http请求都会携带cookie,所以适合存储很小的数据。

localStorage、sessionStorage:保存大量数据,5MB左右

3.http请求不同

cookie:每次http请求都会携带cookie,请求过多保存过多的数据会带来性能问题

web Storage(localStorage和sessionStorage):只在客户端保存,不参与服务器的交互。

4.作用域

cookie:同源窗口共享

localStorage:同源窗口共享

sessionStorage:不在不同的浏览器窗口中共享

5.接口比较

cookie:需要自己封装,原生接口不友好

web Storage(localStorage和sessionStorage):原生接口友好,API接口更方便。也可以再次封装,对Object和Array有更好的支持

6.应用 cookie:不能跨域请求!验证登录、判断是否登陆过网站、保存上次登录的信息、保存上次查看的页面、浏览计数器

localStorage:跨页面传递参数,长期登录,长期保存在本地的数据,数据永久保存除非手动删除

sessionStorage:临时保存数据,页面刷新,敏感账号一次性登录

sessionStorage与页面js数据对象的区别

1)页面中一般的js对象的生存期仅在当前页面有效,因此刷新页面或转到另一页面这样的重新加载页面的情况,数据就不存在了 2)sessionStorage只要同源的同窗口中,刷新页面或进入同源的不同页面,数据始终存在,也就是说只要浏览器不关闭,数据仍然存在

浏览器本地存储与服务器端存储的区别

1)数据既可以在浏览器本地存储,也可以在服务器端存储 2)浏览器可以保存一些数据,需要的时候直接从本地存取,sessionStorage、localStorage和cookie都是由浏览器存储在本地的数据 3)服务器端也可以保存所有用户的所有数据,但需要的时候浏览器要向服务器请求数据 4)服务器端可以保存用户的持久数据,如数据库和云存储将用户的大量数据保存在服务器端 ,服务器端也可以保存用户的临时会话数据,服务器端的session机制,如jsp的session对象,数据保存在服务器上 5)服务器和浏览器之间仅需传递session id即可,服务器根据session id找到对应用户的session对象,会话数据仅在一段时间内有效,这个时间就是server端设置的session有效期 6)服务器端保存所有的用户的数据,所以服务器端的开销较大,而浏览器端保存则把不同用户需要的数据分别保存在用户各自的浏览器中,浏览器端一般只用来存储小数据,而非服务可以存储大数据或小数据服务器存储数据安全一些,浏览器只适合存储一般数据

cookie

1)HTTP Cookie简称cookie,在HTTP请求发送Set-Cookie HTTP头作为响应的一部分。通过name=value的形式存储

2)cookie的构成:

名称:name(不区分大小写,但最好认为它是区分的) 值:value(通过URL编码:encodeURIComponent) 域 路径 失效时间:一般默认是浏览器关闭失效,可以自己设置失效时间 安全标志:设置安全标志后只有SSL连接的时候才发送到服务器 3)cookie的作用:主要用于保存登录信息 4)生命期为只在设置的cookie过期时间之前一直有效,即使窗口或浏览器关闭。 存放数据大小为4K左右 。有个数限制(各浏览器不同),一般不能超过20个。与服务器端通信:每次都会携带在HTTP头中,如果使用cookie保存过多数据会带来性能问题

5)cookie的优点:具有极高的扩展性和可用性

通过良好的编程,控制保存在cookie中的session对象的大小 通过加密和安全传输技术,减少cookie被破解的可能性 只有在cookie中存放不敏感的数据,即使被盗取也不会有很大的损失 控制cookie的生命期,使之不会永远有效。这样的话偷盗者很可能拿到的就 是一个过期的cookie

6)cookie的缺点:

cookie的长度和数量的限制。每个domain最多只能有20条cookie,每个cookie长度不能超过4KB,否则会被截掉 安全性问题。如果cookie被人拦掉了,那个人就可以获取到所有session信息。加密的话也不起什么作用 有些状态不可能保存在客户端。例如,为了防止重复提交表单,我们需要在服务端保存一个计数器。若吧计数器保存在客户端,则起不到什么作用

token

什么是token token的意思是“令牌”,是服务端生成的一串字符串,作为客户端进行请求的一个标识。

当用户第一次登录后,服务器生成一个token并将此token返回给客户端,以后客户端只需带上这个token前来请求数据即可,无需再次带上用户名和密码。

简单token的组成;uid(用户唯一的身份标识)、time(当前时间的时间戳)、sign(签名,token的前几位以哈希算法压缩成的一定长度的十六进制字符串。为防止token泄露)。

token的存储 token可以存到数据库中,但是有可能查询token的时间会过长导致token丢失(其实token丢失了再重新认证一个就好,但是别丢太频繁,别让用户没事儿就去认证)。

为了避免查询时间过长,可以将token放到内存中。这样查询速度绝对就不是问题了,也不用太担心占据内存,就算token是一个32位的字符串,应用的用户量在百万级或者千万级,也是占不了多少内存的。

token的加密 token是很容易泄露的,如果不进行加密处理,很容易被恶意拷贝并用来登录。加密的方式一般有:

在存储的时候把token进行对称加密存储,用到的时候再解密。 文章最开始提到的签名sign:将请求URL、时间戳、token三者合并,通过算法进行加密处理。 最好是两种方式结合使用。

还有一点,在网络层面上token使用明文传输的话是非常危险的,所以一定要使用HTTPS协议。

ES6 新特性

let 关键字、const 关键字、变量的解构赋值、模板字符串、简化对象写法、箭头函数、rest 参数、spread 扩展运算符、Symbol.

Let 与 var 与 const

一、var、let、const的相同点

var、let、const三者都可以声明变量

二、var、let、const的不同

区别一:var 存在变量提升 而 let 与 const 不存在变量提升

区别二:var定义的变量可以声明多次,而let、const定义的变量只能声明

区别三:var、let声明的变量可以再次赋值,而const声明的变量不能再次赋值

区别四:var声明的变量没有自身的作用域,而ler、const声明的变量有自身的作用域

什么是暂时性死区?

 console.log(a);
        var a = 1;

日志输出:undefined
console.log(a);
        let a = 1;
        //‘Uncaught ReferenceError: Cannot access ‘a’ before initialization’
未捕获的引用错误:在初始化之前无法访问“a”

下面我们可以看到这里的日志输出出现了报错,

原因分析: 为什么会出现这种情况呢?这就是我们今天要了解的js中的暂时性死区( temporal dead zone,简称TDZ );

造成该错误的主要原因是:ES6新增的let、const关键字声明的变量会产生块级作用域,如果变量在当前作用域中被创建之前被创建出来,由于此时还未完成语法绑定,如果我们访问或使用该变量,就会产生暂时性死区的问题,由此我们可以得知,从变量的创建到语法绑定之间这一段空间,我们就可以理解为‘暂时性死区’

那么还有别的会造成引发暂时性死区的现象吗? let/const关键字未出现之前,typeof运算符是百分之百安全的,现在也会引发暂时性死区的发生,像import关键字引入公共模块、使用new class创建类的方式,也会引发暂时性死区,究其原因还是变量的声明先与使用

与上述关键字相反、var、function等关键字却不会受到TDZ(暂时性死区)的影响,如果在变量常见之前访问,返回结果为undefined

数组方法

方法名功能返回值是否改变原数组版本
push()(在结尾)向数组添加一或多个元素返回新数组长度YES5unshift()(在开头)向数组添加一或多个元素返回新数组长度YES5
pop()删除数组的最后一位返回被删除的数据YES5
shift()移除数组的第一项返回被删除的数据YES5
reverse()反转数组中的元素返回反转后数组YES5
sort()以字母顺序(字符串Unicode码点)对数组进行排序返回新数组YES5
splice()在指定位置删除指定个数元素再增加任意个数元素 (实现数组任意位置的增删改)返回删除的数据所组成的数组YES5
concat()通过合并(连接)现有数组来创建一个新数组返回合并之后的数组NES5
join()用特定的字符,将数组拼接形成字符串 (默认",")返回拼接后的新数组NES5
slice()裁切指定位置的数组被裁切的元素形成的新数组NES5
toString()将数组转换为字符串新数组NES5
valueOf()查询数组原始值数组的原始值NES5
indexOf()查询某个元素在数组中第一次出现的位置返存在该元素,返回下标,不存在 返回 -1NES5
lastIdexOf()反向查询数组某个元素在数组中第一次出现的位置存在该元素,返回下标,不存在 返回 -1NES5
forEach()(迭代) 遍历数组,每次循环中执行传入的回调函数无/(undefined)NES5
map()(迭代) 遍历数组, 每次循环时执行传入的回调函数,根据回调函数的返回值,生成一个新的数组有/自定义NES5
filter()(迭代) 遍历数组, 每次循环时执行传入的回调函数,回调函数返回一个条件,把满足条件的元素筛选出来放到新数组中满足条件的元素组成的新数组NES5
every()(迭代) 判断数组中所有的元素是否满足某个条件全都满足返回true 只要有一个不满足 返回falseNES5
some()(迭代) 判断数组中是否存在,满足某个条件的元素只要有一个元素满足条件就返回true,都不满足返回falseNES5
reduce()(归并)遍历数组, 每次循环时执行传入的回调函数,回调函数会返回一个值,将该值作为初始值prev,传入到下一次函数中最终操作的结果NES5
reduceRight()(归并)用法同reduce,只不过是从右向左同reduceNES5
includes()判断一个数组是否包含一个指定的值.是返回 true,否则falseNES6
Array.from()接收伪数组,返回对应的真数组对应的真数组NES6
find()遍历数组,执行回调函数,回调函数执行一个条件,返回满足条件的第一个元素,不存在返回undefined满足条件第一个元素/否则返回undefinedNES6
findIndex()遍历数组,执行回调函数,回调函数接受一个条件,返回满足条件的第一个元素下标,不存在返回-1满足条件第一个元素下标,不存在=>-1NES6
fill()用给定值填充一个数组新数组NES6
flat()用于将嵌套的数组“拉平”,变成一维的数组。返回一个新数组NES6
flatMap()flat()和map()的组合版 , 先通过map()返回一个新数组,再将数组拉平( 只能拉平一次 )返回新数组NES6

Json 如何新增/删除键值对

<script>\
var test = { name: "name", age: "12" };\
var countrys = {\
"newval": [{ "Country_code": "101", "Country_name": "中国" },\
{ "Country_code": "102", "Country_name": "美国" }]\
};\
$(function () {\
test.id = "1245";\
alert(test.id);

//添加\
var c = { "Country_code": "103", "Country_name": "英国" };\
countrys.newval.push(c)\
alert(countrys.newval[0].Country_name)\
//删除\
delete countrys.newval[1];\
alert(countrys.newval[0].Country_name)\
})\
</script>

面向对象

两大编程思想

面向过程编程 POP(Process-oriented programming)

面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候再一个一个的依次调用就可以了。

面向对象编程 OOP (Object Oriented Programming)

面向对象是把事务分解成为一个个对象,然后由对象之间分工与合作。 面向对象是以对象功能来划分问题,而不是步骤。

对象 现实生活中:万物皆对象,对象是 一个具体的事物,看得见摸得着的实物。例如,一本书、一辆汽车、一个人可以是“对象”,一个数据库、一张网页、一个与远程服务器的连接也可以是“对象”。

在 JavaScript 中,对象是一组无序的相关属性和方法的集合,所有的事物都是对象,例如字符串、数值、数组、函数等。

对象是由属性和方法组成的:

属性:事物的 特征,在对象中用 属性 来表示(常用名词) 方法:事物的 行为,在对象中用 方法 来表示(常用动词)

类 class 在 ES6 中新增加了类的概念,可以使用 class 关键字声明—个类,之后以这个类来实例化对象。

类 抽象了对象的公共部分,它泛指某一大类(class)对象 特指某一个,通过类实例化一个具体的对象

类的继承

3.1 继承

程序中的继承:子类可以继承父类的一些属性和方法。

super 关键字

super 关键字用于访问和调用对象父类上的函数。可以调用父类的构造函数,也可以调用父类的普通函数

注意: 子类在构造函数中使用super, 必须放到 this 前面  (必须先调用父类的构造方法,在使用子类构造方法)

class Person {   // 父类
      constructor(surname){
         this.surname = surname;
     }
} 
class  Student extends Person {       // 子类继承父类
     constructor(surname,firstname){
          super(surname);             // 调用父类的constructor(surname)
	this.firstname = firstname; // 定义子类独有的属性
     }
} 

三个注意点:

  1. 在ES6中类没有变量提升,所以必须先定义类,才能通过类实例化对象

  2. 类里面的共有属性和方法一定要加 this使用

  3. 类里面的this指向:

    • constructor 里面的 this指向实例对象
    • 方法里面的this指向这个方法的调用者

面向对象的思维特点:

  • 抽取(抽象)对象共用的属性和行为组织(封装)成—个类(模板)
  • 对类进行实例化,获取类的对象

2.实例:程序使用类创建对象时,生成的对象叫类的实例。由类创建对象实例的过程叫做实例化。

3.对象定义:可以把对象理解为属性的集合,每个属性存放一个原始值、对象或函数。

4.面向对象:面向对象编程 —— Object Oriented Programming,简称 OOP ,是一种编程开发思想。 它将真实世界各种复杂的关系,抽象为一个个对象,然后由对象之间的分工与合作,完成对真实世界的模拟。

面向对象的特性:

  • 抽象:

    • a)抽象是面向对象最为重要的特征。对象本身的状态与行为,以及对象之间的关系,都是抽象的结果。没有抽象,就没有对象,也就谈不上面向对象了。抽象是一种思维习惯,因此,抽象是面向对象的第一特征。
    • b)把同类的对象共有的属性或方法抽出封装成单独的对象,在用到的时候给相应的对象使用;
  • 封装性

    • 概述:将对象的属性和实现细节隐藏起来,不让外部程序直接进行访问,将属性私有化,仅对外公开接口,让外部程序通过类提供的方法来对隐藏信息进行访问和操作。好处是外部程序只能通过类规定的方法对数据进行访问,避免外界程序对类内部属性进行破坏。
  • 继承性

    • 继承可以对代码进行复用以提高编程的效率,继承就是子类获取父类的成员变量及成员方法。已经存在的类称为父类(也叫基类,超类),新构建的类称为子类(派生类)

    • 1.父类可以有多个子类,但子类只能继承一个父类(单继承,Java中不支持多继承)。一个子类不能继承多个父类,但可以实现多个接口。

      2.子类不能继承父类中private修饰的属性和方法。

      3.子类可以重写父类的方法。

  • 多态性

    • 多态指调用同一个方法,不同的对象可能会有不同的行为
    • 比如在js中的‘+’,在字符串中是链接作用,在数字中间是加法运算;
    • 多态最常见的三种方法:重载,重写,接口

面向过程和面向对象的对比

image.png

new 操作符做了哪些事情

new 操作符新建了一个空对象,

这个对象原型指向构造函数的prototype,

执行构造函数后返回这个对象。

普通函数和构造函数的区别

1.构造函数也是一个普通函数,创建方式和普通函数一样,但是构造函数习惯上首字母大写

2.调用方式不一样,普通函数直接调用,构造函数要用关键字 new 来调用

3.调用时,构造函数内部会创建一个新对象,就是实例,普通函数不会创建新对象

4.构造函数内部的 this 指向实例,普通函数内部的 this 指向调用函数的对象(如果没有对象调用,默认为 window)

5.构造函数默认的返回值是创建的对象(也就是实例),普通函数的返回值由 return 语句决定

6.构造函数的函数名与类名相同

this指向问题

image.png

this,call,apply,bind

image.png

bind

bind方法返回的是一个修改过后的函数。

bind也可以有多个参数,并且参数可以执行的时候再次添加,但是要注意的是,参数是按照形参的顺序进行的。

总结:

call和apply都是改变上下文中的this并立即执行这个函数,bind方法可以让对应的函数想什么时候调就什么时候调用,并且可以将参数在执行的时候添加,这是它们的区别,根据自己的实际情况来选择使用。

原型/原型链/(原型)继承

原型: 原型分为隐式原型和显式原型,每个对象都有一个隐式原型,它指向自己的构造函数的显式原型。

原型链: 多个__proto__组成的集合成为原型链

所有实例的__proto__都指向他们构造函数的prototype 所有的prototype都是对象,自然它的__proto__指向的是Object()的prototype 所有的构造函数的隐式原型指向的都是Function()的显示原型 Object的隐式原型是null

对象原型(__proto__)和构造函数(prototype)原型对象里面都有一个属性 constructor 属性 ,constructor 我们称为构造函数,因为它指回构造函数本身。

image.png

JavaScript 的成员查找机制(规则)

当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性。

如果没有就查找它的原型(也就是 proto 指向的 prototype 原型对象)。

如果还没有就查找原型对象的原型(Object)的原型对象)。

依此类推一直找到 Object 为止(null)。

proto 对象原型的 意义就在于为对象成员查找机制提供一个方向,或者说一条路线。

原型对象this指向

构造函数中的 this指向我们的实例对象

原型对象里面放的是方法,这个方法里面的this指向的是这个方法的调用者,也就是这个实例对象

继承

原型链继承

通过实例化一个函数使子类的原型指向父类的实例,子类就可以调用到父类的属性和方法。

  • 优点:写法方便简洁、容易理解;
  • 缺点:在父类型构造函数中定义的引用类型值的实例属性,会在子类型原型上变成原型属性被所有子类型原型上变成原型属性被所有子类型实例所共享。同时在创建子类型的实例时,不能向超类型的构造函数中传递参数。

借用构造函数继承

利用 call 或者 apply 把父类中通过this指定的方法复制到子类创建的实例中。

优点:

  • 解决了引用类型的值被实例共享的问题
  • 可以向超类传递参数
  • 可以是按多继承(call若干个超类)

缺点

  • 不能继承超类原型上的属性和方法
  • 无法实现函数复用,由于call有很多个父类实例的副本,性能损耗。
  • 原型链丢失

组合模式继承

是将原型链继承和构造函数继承二者取其长处组合到一起而产生的继承模式

优点

  • 即通过在原型上定义方法实现了函数复用,又保证每个实例都有自己的属性
  • 解决了原型链继承和借用构造函数继承造成的影响。

缺点 导致无论在什么情况下都会调用两次超类型构造函数:一次是在创建子类型原型的时候;一次是在子类型构造函数的内部。

共享原型继承

使得子类和父类共用一个原型。

优点

  • 简单!!!

缺点

  • 只能继承父类原型属性方法,不能继承构造函数属性方法
  • 与原型链继承一样0存在引用类型问题

原型式继承

多用于基于当前已有对象创建新对象

优点

  • 不需要单独创建构造函数

原型式继承的缺点

  • 属性中包含的引用值始终会在相关对象间共享。

寄生式继承

结合原型式继承和工厂模式,创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后返回对象。

优点

写法简单,不需要单独创建函数。

寄生式继承的缺点

通过该方式给对象添加函数会导致函数难以复用。

寄生组合式继承

通过借用构造函数来继承属性,通过原型链的混成形式来实现继承的方法。

本质上就是使用寄生式继承来继承超类的原型,然后再将结果指定给子类型的原型。

优点

高效率只调用一次父构造函数,并且因此避免了在子原型上面创建的不必要、多余的属性。与此同时,原型链还保持不变。

寄生组合式继承的缺点

代码复杂!!!

ES6中class的继承(新)

实质就是先将父类实例对象上的方法和属性,加到this上面(所以必须调用super()方法),然后再用子类的构造函数修改this。
需要注意的是:class关键字只是原型的语法糖,js继承依然是基于原型实现的。

优点

语法简单易懂,操作更加方便

class继承的缺点

不是每个浏览器都支持class关键字

.Promise

一、什么是 Promise?

我们都知道,Promise 是承诺的意思,承诺它过一段时间会给你一个结果。

Promise 是一种解决异步编程的方案,相比回调函数和事件更合理和更强大。

从语法上讲,promise 是一个对象,从它可以获取异步操作的消息;

二、promise 有三种状态

pending 初始状态也叫等待状态,

fulfiled成功状态,

rejected 失败状态;

状态一旦改变,就不会再变。创造 promise实例后,它会立即执行。

三、Promise 的两个特点

1.Promise 对象的状态不受外界影响

2.Promise 的状态一旦改变,就不会再变,任何时候都可以得到这个结果,状态不可以逆,

四、Promise 的三个缺点 1)无法取消 Promise,一旦新建它就会立即执行,无法中途取消

2)如果不设置回调函数,Promise 内部抛出的错误,不会反映到外部

3)当处于 pending(等待)状态时,无法得知目前进展到哪一个阶段,是刚刚开始还是即将完成

reject的用法

以上是对promise的resolve用法进行了解释,相当于resolve是对promise成功时候的回调,它把promise的状态修改为fullfiled,那么,reject就是失败的时候的回调,他把promise的状态修改为rejected,这样我们在then中就能捕捉到,然后执行“失败”情况的回调。then方法可以接受两个参数,第一个对应resolve的回调,第二个对应reject的回调。

catch

与Promise对象方法then方法并行的一个方法就是catch,与try  catch类似,catch就是用来捕获异常的,也就是和then方法中接受的第二参数rejected的回调是一样的,

all方法

与then同级的另一个方法,all方法,该方法提供了并行执行异步操作的能力,并且在所有异步操作执行完后并且执行结果都是成功的时候才执行回调。

race的用法

all是等所有的异步操作都执行完了再执行then方法,那么race方法就是相反的,谁先执行完成就先执行回调。先执行完的不管是进行了race的成功回调还是失败回调,其余的将不会再进入race的任何回调

promise 是用来解决两个问题的:

1.回调地狱,代码难以维护, 常常第一个的函数的输出是第二个函数的输入这种现象

2.promise 可以支持多并发的请求,获取并发请求中的数据这个 promise 可以解决异步的问题,本身不能说promise 是异步的

简述 async 的用法

async/await 是ES7提出的基于promise的解决异步的最终方案。

async

async是一个加在函数前的修饰符,用于表示函数是一个异步函数,该函数的执行不会阻塞后面代码的执行,被async定义的函数会默认返回一个Promise对象resolve的值。因此对async函数可以直接then,返回值就是then方法传入的函数。

await await 也是一个修饰符,只能放在async定义的函数内。可以理解为等待。

await 修饰的如果是Promise对象:可以获取Promise中返回的内容(resolve或reject的参数),且取到值后语句才会往下执行;

如果不是Promise对象:把这个非promise的东西当做await表达式的结果。

promise async/await 区别

1 promise是ES6,async/await是ES7

2 async/await相对于promise来讲,写法更加优雅

3 reject状态:

1)promise错误可以通过catch来捕捉,建议尾部捕获错误,

2async/await既可以用.then又可以用try-catch捕捉

联系:

1。async/await是基于Promise实现的,所以async/await与Promise一样,不会阻塞代码执行. 但是promise函数的返回值需要在.then和.catch中处理,async就不需要.then来处理返回值.

2-函数相互调用时,promise会引起回调地狱问题,async函数中的await可以使异步代码看起来像同步代码,改变函数写法解决回调地狱

get请求传参长度的误区

一、写在前面

我们常常说get请求参数的大小存在限制,而post请求的参数大小是没有限制的。

但是作为http协议下的几种请求类型,我们需要明确以下几点。

二、内容

1、http协议并没有规定GET请求和POST请求的长度

2、GET的最大长度显示是因为浏览器和web服务器限制了URI的长度

3、不同的浏览器和web服务器,限制的最大长度都是不一样的。

4、要支持IE,则最大长度为2083byte,如果只支持chrome,则最大长度为8182byte。

client/scroll/offsetHeight,scrollTop, offsetTop,clientTop 的区别

image.png clientHeight:表示的是可视区域的高度,不包含 border 和滚动条

image.png offsetHeight: 表 示 可 视 区 域 的 高 度 , 包 含 了 border 和 滚 动 条

image.png scrollHeight:表示了所有区域的高度,包含了因为滚动被隐藏的部分。

image.png clientTop:表示边框 border 的厚度,在未指定的情况下一般为0

scrollTop:滚动后被隐藏的高度,获取对象相对于由 offsetParent 属性指定的父坐标(css 定位的元素或 body 元素)距离顶端的高度。 image.png offsetTop: 当前元素顶部距离最近父元素顶部的距离,和有没有滚动条没有关系。单位px,只读元素。 image.png

js拖拽功能的实现

onmousedown、onmousemove,onmouseup或者drag,drop

(drag,drop) onmousedown、onmousemove,onmouseup

方式一: 如果要设置物体拖拽,那么必须使用三个事件,并且这三个事件的使用顺序不能颠倒。

1.onmousedown:鼠标按下事件

2.onmousemove:鼠标移动事件

3.onmouseup:鼠标抬起事件

拖拽的基本原理就是根据鼠标的移动来移动被拖拽的元素。鼠标的移动也就是x、y坐标的变化;元素的移动就是style.position的top和left的改变。当然,并不是任何时候移动鼠标都要造成元素的移动,而应该判断鼠标左键的状态是否为按下状态,是否是在可拖拽的元素上按下的。

拖拽状态 = 0鼠标在元素上按下的时候{
      拖拽状态 = 1
      记录下鼠标的x和y坐标
      记录下元素的x和y坐标
}
鼠标在元素上移动的时候{
      如果拖拽状态是0就什么也不做。
      如果拖拽状态是1,那么
      元素y = 现在鼠标y - 原来鼠标y + 原来元素y
      元素x = 现在鼠标x - 原来鼠标x + 原来元素x
}
鼠标在任何时候放开的时候{
       拖拽状态  = 0
}
举个例子
<div class="drag" style="left:0;top:0;width:100px;height:100px">按住拖动</div>
<style>
        .drag {
            background-color: skyblue;
            position: absolute;
            line-height: 100px;
            text-align: center;
        }
 </style>
// 获取DOM元素  
      let dragDiv = document.getElementsByClassName("drag")[0];
      // 鼠标按下事件 处理程序
      let putDown = function (event) {
          dragDiv.style.cursor = "pointer";
          let offsetX = parseInt(dragDiv.style.left); // 获取当前的x轴距离
          let offsetY = parseInt(dragDiv.style.top); // 获取当前的y轴距离
          let innerX = event.clientX - offsetX; // 获取鼠标在方块内的x轴距
          let innerY = event.clientY - offsetY; // 获取鼠标在方块内的y轴距
          // 按住鼠标时为div添加一个border
          dragDiv.style.borderStyle = "solid";
          dragDiv.style.borderColor = "red";
          dragDiv.style.borderWidth = "3px";
          // 鼠标移动的时候不停的修改div的left和top值
          document.onmousemove = function (event) {
              dragDiv.style.left = event.clientX - innerX + "px";
              dragDiv.style.top = event.clientY - innerY + "px";
              // 边界判断
              if (parseInt(dragDiv.style.left) <= 0) {
                  dragDiv.style.left = "0px";
              }
              if (parseInt(dragDiv.style.top) <= 0) {
                  dragDiv.style.top = "0px";
              }
              if (parseInt(dragDiv.style.left) >= window.innerWidth - parseInt(dragDiv.style.width)) {
                  dragDiv.style.left = window.innerWidth - parseInt(dragDiv.style.width) + "px";
              }
              if (parseInt(dragDiv.style.top) >= window.innerHeight - parseInt(dragDiv.style.height)) {
                  dragDiv.style.top = window.innerHeight - parseInt(dragDiv.style.height) + "px";
              }
          }
          // 鼠标抬起时,清除绑定在文档上的mousemove和mouseup事件
          // 否则鼠标抬起后还可以继续拖拽方块
          document.onmouseup = function () {
              document.onmousemove = null;
              document.onmouseup = null;
              // 清除border
              dragDiv.style.borderStyle = "";
              dragDiv.style.borderColor = "";
              dragDiv.style.borderWidth = "";
          }
      }
      // 绑定鼠标按下事件
      dragDiv.addEventListener("mousedown", putDown, false);

方法二: H5新增了一个draggable属性,用于对需要拖拽的元素进行标记,定义了这个属性后就可以定义拖拽事件了

image.png

<div class="wrapper">
    <section class="show">
      <h1>拖动你喜欢的图片</h1>
      <div class="tshirt">
        <img src="" alt="">
        <div class="empty"></div>
      </div>
    </section>
    <section class="patterns">
      <img draggable="true" src="./pic1.jpeg" alt="蜡笔小新">
      <img draggable="true" src="./pic2.jpeg" alt="折木奉太郎">
      <img draggable="true" src="./pic3.jpeg" alt="樱木花道">
    </section>
 </div>
<style>
    .wrapper {
      display: flex;
    }
    .show {
      width: 550px;
      height: 550px;
      background-color: #eee;
    }
    .patterns img{
      width: 300px;
      height: 300px;
      margin-left: 50px;
      border: 4px solid #eee;
    }
    .empty {
      width: 300px;
      height: 300px;
    }
    .empty img {
      width: 300px;
      height: 300px;
    }
  </style>
  <script>
    const empty = document.querySelector('div.empty')
    const h1 = document.querySelector('h1')
    let name

    // 拖动事件开始
    document.addEventListener('dragstart', (e) => {
      name = e.target.alt
    }, false)

    document.addEventListener('drag', (e) => {
      // 拖拽开始时添加边框
      e.target.style.border = '5px dashed red'
      empty.style.border = '5px dashed red'
    })

    document.addEventListener('dragend', (e) => {
      // 拖拽结束时边框消失
      e.target.style.border = 'none'
      empty.style.border = 'none'
      h1.innerHTML = '拖动你喜欢的图片'
      h1.style.color = 'black'
    }, false)
//当拖拽元素进入empty元素是,需要判断empty是否有子节点,如果有则删除,然后将标题设置为上面拖拽开始时保存的name属性并且改变字体颜色
    empty.addEventListener('dragenter', (e) => {
      if (empty.firstChild) {
        empty.removeChild(empty.firstChild)
      }
      h1.innerHTML = name
      h1.style.color = 'red'
    }, false)
//当拖拽元素放置时需要将这个图片放置到empty元素里面,也就是用appendChild将img加入元素中,drop事件跟dragover事件都调用了preventDefault事件来阻止默认事件的发送,那是因为浏览器默认不能在拖拽以后进行放置,所以需要我们手动的取消默认事件。
    empty.addEventListener('dragover', e => {
      e.preventDefault()
    }, false)

    empty.addEventListener('drop', e => {
      e.preventDefault()
      e.target.appendChild(document.querySelector(`img[alt=${name}]`))
    }, false)

  </script>

image.png 拖拽结束时将边框设置为none,并且改变标题文本及相关样式,效果如下:

image.png

image.png

JS 监听对象属性的改变

监听对象属性的改变

ES5中,通过defineProperty()的set进行监听

在ES5中新增了一个Object.defineProperty,直接在一个对象上定义一个新属性,或者修改一个已经存在的属性, 并返回这个对象。

// Object.defineProperty() 方法直接在一个对象上定义一个新属性,或者修改一个已经存在的属性, 并返回这个对象
Object.defineProperty(obj, prop, descriptor)
// obj 需要定义属性的对象。
// prop 需被定义或修改的属性名。
// descriptor 需被定义或修改的属性的描述符,Object类型

descriptor下有几个属性

writable是否允许被修改,默认为false,不允许
enumerable属性是否可以被枚举,默认为false,支持
configurable是否属性可以被删除和重新定义特性,默认false不允许
get,获取值的时候的方法,类型为 function ,获取值的时候会被调用,不设置时为 undefined
set,设置值的时候的方法,类型为 function ,设置值的时候会被调用,undefined
当使用了getter或setter方法,不允许使用writable和value这两个属性

ES6中,通过Proxy实现

// target为要代理的对象
// handler为操作
const p = new Proxy(target, handler)

Object.defineProperty与Proxy区别

Proxy监听对象;Object.defineProperty监听具体属性

Proxy可以监听数组的变化,拦截方法也多

Proxy返回的是一个新对象,我们只能操作这个新对象,不会影响旧对象的属性;Object.defineProperty直接影响

js 语言特性

JS的语言特征 前言 JS是一种弱类型的,解释型的脚本语言

弱类型 定义:在定义变量的时候,我们可以为变量赋值任何数据,变量的数据类型不是固定死的,这样的类型叫做弱类型。

var a = 10; a = "abc"; a = []; a = function(){};

优点: 弱类型使用简单,更灵活多变。

缺点: 因为不包含类型信息,所以在代码的上下文中,可能会进行隐含的类型转换,比如把字符串转整型,整型转字符串,这样会稍损性能,并且可能会不符合程序本意。注:(typescript可以稍微弥补一些)

强类型 定义:在声明变量的时候,一旦给变量赋值,那么变量的数据类型就已经确定,之后如果要给该变量赋值其他类型的数据,需要进行强制数据类型转换。

int a = 10; a = "10";

优点: 强类型语言由于类型之间不可隐式转换而更加可靠。(更严谨)

缺点: 强类型语言要求完全显示转换,开发效率低。

解释型 定义:程序不需要编译,程序在运行时才翻译成机器语言,每执 行一次都要翻译一次。因此效率比较低。在运行程序的时候才翻译,专门有一个解释器去进行翻译,每个语句都是执行的时候才翻译。

优点:解释型语言可以写完一行,或一小段程序之后,马上运行,马上调试,快速的测试自己的想法。并且跨平台性好。

缺点:效率比较低。

编译型? 定义:程序在执行之前需要一个专门的编译过程,把程序编译成 为机器语言的文件,运行时不需要重新翻译,直接使用编译的结果就行了。

优点:编译型的语言在运行期间一般是要比解释型的要快一点,因为编译型的语言已经是机器码,无需要再进行解释为机器码。程序执行效率高些。

缺点:跨平台性差。

原文链接:blog.csdn.net/zzh12519944…

js 全排列

回溯(递归); 交换(递归); 链接(递归);  参考链接:t.csdn.cn/pC0Wh