ThinkChat🤖让你学习和工作更高效,注册即送10W Token,即刻开启你的AI之旅 广告
# JavaScript ## 概念 ### AJAX 广义上的 AJAX 是指 XMLHttpRequest,也就是 ECMAScript 中用于网络交互的操作。 现在的 AJAX 大多数指的是 JQuery 中的 AJAX 函数。 JQuery 中的 AJAX 封装了大量网络交互操作,使其更加方便快捷。 ### this 关键字 首先介绍一下函数不同的调用方法: 普通函数调用;作为方法来调用;作为构造函数来调用;使用apply/call方法来调用;Function.prototype.bind方法;es6箭头函数。 > 谁调用了方法,this 就指向谁。 **普通函数调用** ```javascript function person() { this.name = "xiaoming"; console.log(this); console.log(this.name); } person(); //print window, xiaoming ``` 在这段代码中,person() 作为普通函数调用,实际上 person 是作为全局对象 window 的一个方法来进行调用的,即 window.person()。 所以在这里,是 window 调用了 person 方法,那么 person() 中的 this 就指向 window,并且 window 还有了一个属性 name,值为 xiaoming。 同样的: ```javascript var name = "xiaoming" function person() { console.log(this); console.log(this.name); } person(); // print window, xiaoming ``` 和上边代码的结果是相同的,因为 this 指向 window, 所以 this.name 的值还是 xiaomimg。 **作为方法来调用** ```javascript var name = "xiao hong"; var person = { name: "xiao ming", printName: function() { console.log(this.name); }, } // print xiao ming // person 对象调用 printName() 方法,所以 this 指向 person 对象,所以打印 xiao ming。 person.printName(); var printName = person.showName; // 注意没有 () // print xiao hong // 将 person 类中的方法 printName() 赋给了 printName 变量,当直接调用 printName() 的时候,this 指向的是 window,所以打印 xiao hong。 printName(); ``` **作为构造函数来调用** ```javascript function Person(name) { this.name = name; } var person1 = Person("xiao ming"); console.log(person1.name); // error:undefine // 因为没有使用 new 操作,等同于 window 调用了 Person() 方法,因此 this 指向的是 window对象。 console.log(window.name); // xiao ming var person2 = new Person("xiao hong") console.log(person2.name); // xiao hong console.log(window.name); // xiao ming ``` 下边讲解一下 new 操作: ```javascript function Person(name) { var o = {}, o.__proto__ = Person.prototype; // 原型继承 Person.call(o, name); return o; } // 注意,此处为伪代码,并不能实际运行,如此写是为了让读者更明白 new 操作符的作用。 ``` **call/apply 方法的调用** call、apply 方法的作用是改变 this 的作用域。 ```javascript var name = "xiao hong"; var Person = { name: "xiao ming", printName: function() { console.log(this.name); } } Person.printName(); // xiao ming Persin.printName.call(); // xiao hong ``` **箭头函数(ES6)** 箭头函数的 this 指向固定化,始终指向外部对象,因为箭头函数没有 this,因此它自身不能进行 new 操作实例化,同时也不能使用 call、apply、bind 来改变 this 的指向。 ## 操作符 ### == && === 例:value1 == value2 , value3 === value4, == : 1. 如果两个值类型相同,再进行三个等号(===)的比较。 2. 如果两个值类型不同,也有可能相等,需根据以下规则进行类型转换在比较: 1. 如果一个是null,一个是undefined,那么相等。 2. 如果一个是字符串,一个是数值,把字符串转换成数值之后再进行比较。 ===: 1. 如果类型不同,则一定不相等。 2. 如果两个都是数值并且是同一个值,那么相等。 3. 如果一个是数值一个是 NaN,那么不相等。 4. 如果都是true 或者 false 相等。 5. 如果都是 null 或者 undefined,那么相等。注意,null == undefined => true, null === undefined => false. 总结: 1. string、number 等基础类型,== 与 === 的区别在于,== 会转换类型再进行比较,=== 不会转换类型比较。 2. array、object 等高级类型,== 与 === 没有区别。 3. 基础类型与高级类型比较,区别在于,== 将高级转换为基础类型,再进行比较,=== 因为类型不同,直接返回false ### equals JavaScript 中字符串没有 equals 方法,但是可以自己实现。 ## 函数 ### ready() && onload() - document.ready() 是DOM结构绘制完成之后即可执行,不需要相关联资源下载完成,例如图片DOM结构绘制完成,而图片可能没有下载完成,其函数同样会执行。 - document.onload() 相关资源都加载完毕之后,执行,一个页面中只能有一个onload - load函数 常用在图片的加载,比如 $('idName').load(),load同样是资源全部加载完成之后才会执行。 > JavaScript 没有 document.ready(),但是有 document.readyState,返回当前页面情况。 ### apply && call && bind #### 相同点: - 都是用来改变函数的this对象的指向的 - 第一个参数都是this要指向的对象 - 都可以继续传递参数 ```javascript var xb = { name: '小冰', gender: '女', say: function(){ alert(this.name + ',' + this.gender); } } var other = { name: '小东', gender: '男', } xb.say(); ===> 小冰,女 xb.say.call(other); ==> 小东,男 xb.say.apply(other); xb.say.bind(other)(); ``` #### 三个方法的不同点 - `call`和 `apply`方法,都是对函数的直接调用,但是 `bind()` 方法需要加上()来执行 - `call`和 `apply`方法传参的方法不同 ```javascript xb.say.call(other,'斯坦福','3')。 xb.say.apply(other, 'sitanfu','third'); ``` - `bind` 返回的是方法,所以需要后边加上()才能执行。 #### 实现 bind 方法 ```javascript if (!function() {}.bind) { Function.prototype.bind = function(context) { var self = this, args = Array.prototype.slice.call(arguments); return function() { return self.apply(context, args.slice(1)); } }; } ``` ### Typeof Typeof 把参数的类型信息作为字符串返回。 可能的值有: - number - string - boolean - object - function - undefined > 注意:typeof(null) 返回object。 ### 定时器 - `setTimeout(.... , time)` 在指定的时间(time) 后调用函数或者计算表达式。 - `setInterval(code,millisec,lang)`, lang传递给执行函数的其他参数。会在指定的周期(millisec)不断调用函数(code),直到 clearInterval() 清除。 ### slice && splice (操作数组) #### slice (截取数组,不改变原数组) > 接收一个或两个参数,它可以创建一个由当前数组中的一项或多项组成的新数组,也就是说它不会修改原来数组的值。 用法: - slice( para1 ),会截取从para1开始的到原数组最后的部分; - slice(para1,para2)会截取原数组的从para1开始的para2-para1个数组。 > 注意:当两个参数中存在负数时,用原数组的长度加上两个负数的参数作为相应的参数来计算,并且得到的第一个参数必须小于第二个参数,否则返回一个空数组。 ```javascript var a = [1,2,3,4,5,6]; a.slice(-2,-3) //return [] a.slice(-3,-2) //return [4] ``` #### splice (删除/替换 数组中的某几位,返回删除的数组) 用法: - splice( para1,para2 ) : 删除数组中任意数量的项,从para1开始的para2项。 > 注意的是用splice删除数组中的值会直接将某几项从数组中完全删除,会导致数组length值的改变,这与delete的删除置为undefined是不一样的。 - splice( para1,para2,val1,val2… ):向数组中添加和删除项,para1表示可以添加的项数,para2表示删除的项数,后面的变量表示要添加的项的值,注意是从para1之后开始删除和添加的。 > 参数为负数的问题,如果para1为负数,则会加上数组的长度作为para1的值,而para2为负数或0的话不会执行删除操作,此时从 para1 当前位置开始添加变量。 ### split (操作字符串) 根据特定的字符切割字符串并返回切割后生成数组。 > 如果 a.split(''), 则切割每一个字符。 ```javascript var string = 'abcde'; var array = string.split(''); // array = ['a','b','c','d','e'] ``` ### reverse (反转数组中的元素) ```javascript var a = [1,2,3,4]; a.reverse(); //a = [4,3,2,1] ``` ### join (用于将数组转换为字符串) > 不改变原数组 ```javacript var a = [1,2,3,4]; a.join('') // return '1234' a.join('.') // return '1.2.3.4' //此时,a = [1,2,3,4] ``` ## 综合 ### 垃圾回收机制 Js具有自动回收垃圾的机制,垃圾收集器会按照固定的时间间隔周期性执行。 - 标记清除 工作原理:当变量进入环境时,将这个变量标记为“进入环境”,当变量离开环境时,则将其标记为“离开环境”。标记为“离开环境”的就会被回收。 - 引用计数 跟踪记录每个值被引用的次数,当一个值被引用的次数为0时,垃圾收集器下次运行时就会将值回收。 ### 内存泄漏 | 问题 | 解决办法| |-------------------------------------------------------|---------------| |全局变量引起的内存泄漏 |严格使用全局变量| |闭包引起的内存泄漏。闭包可以维持函数内部变量,使其得不到释放|避免闭包,或者在外部事件处理函数中删除对DOM的引用| |被遗忘的定时器或者回调函数 |删除定时器| ### JS对象属性遍历 1. 使用 Object.keys(objectName).forEach(function(element,index){ .... } 便利 2. for( var i in object ) {...} 3. Object.getOwnPropertyNames(), 返回一个数组,包含自身的所有属性(不包含symbol属性,但是包含不可枚举属性)。 4. Reflect.ownKeys(obj) ,包含所有属性,包含symbol属性和不可枚举属性 ```javascript var obj = { a: 'aaaaa', b: 'bbbbb', c: 'ccccc', } for ( var i in obj ) { console.log(i, obj[i]); } //输出结果 //a aaaaa //b bbbbb //c ccccc Object.keys(obj).forEach( function(ele,index) { console.log(ele,obj[ele],index) }) //输出结果 a dfafda 0 b afdaf 1 c ssssss 2 ``` ### cookies && storage `cookies`、`localStorage`、`sessionStorage` 都是在客户端以键值对存储的存储机制,并且只能将值存储为字符串。 `sessionStorage`常用操作方法: - session.clear(), 清空 - session.setItem('key','value') - session.getItem('key') - session.removeItem('key') - session.length, | |cookies | localStorage | sessionStorage| |--------------|--------|--------------|---------------| |初始化 |客户端或服务器,服务器可使用Set-Cookies请求头|客户端|客户端| |过期时间 |手动设置|永不过期 |当前页面刷新、关闭| |与域名关联 |是 |否 |否| |容量 |4kb |5mb |5mb| |随请求发给服务器|是 |否 |否| ### 冒泡/捕获/事件委托/事件代理 `事件委托` 就是利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。 举例: 有三个同事预计会在周一收到快递。为签收快递,有两种办法:一是三个人在公司门口等快递;二是委托给前台MM代为签收。现实当中,我们大都采用委托的方案(公司也不会容忍那么多员工站在门口就为了等快递)。前台MM收到快递后,她会判断收件人是谁,然后按照收件人的要求签收,甚至代为付款。这种方案还有一个优势,那就是即使公司里来了新员工(不管多少),前台MM也会在收到寄给新员工的快递后核实并代为签收。 1. 现在委托前台的同事是可以代为签收的,即程序中的现有的dom节点是有事件的; 2. 新员工也是可以被前台MM代为签收的,即程序中新添加的dom节点也是有事件的。 发生事件的元素可以使用 `ev.target` 获取到,利用事件冒泡。 [参考文章](https://www.cnblogs.com/liugang-vip/p/5616484.html) > 注意:$("ul").on("click","li",function(){});这样写有事件委托。$("ul li").on();这样写法则没有。 ### 浏览器事件模型 1. 每个浏览器都有自己的事件模型,W3C为了兼顾标准,定义了三个阶段:捕获阶段,目标阶段,冒泡阶段。 2. elem.addEventListener("eventType", fn , boolean); 3. 参数说明: 事件类型,函数,true,表示在捕获阶段执行,false为在冒泡阶段执行(默认为false). 4. stopPropagation()可以阻止事件的传播,可以是捕获阶段也可以是冒泡阶段。 ### 模块化 Commonjs、AMD、CMD、es6 modules - CommonJS - 核心思想就是通过 require 方法来同步加载所要依赖的其他模块,然后通过 exports 或者 module.exports 来导出需要暴露的接口 - 同步加载 - 用于nodejs服务器端 - AMD规范 - require.js - 非同步加载,允许指定回调函数 - require([module],callback),引用函数 - define(id, [dependences], callback), 定义函数 - 支持 Commonjs 的导出方式 - 只是提前加载所有依赖,不是按需加载 - CMD规范 - sea.js - 按需加载 - 依赖spm打包 - ES6模块化 - import - es6 modules - 前端框架中常用 ### 闭包 简答的说,如果一个function能除了能访问它的参数、局部变量或函数之外,还能访问外部环境变量(包括全局变量、DOM、外部函数的变量或者函数),那么它就可以成为一个闭包。 ```javascript // 看代码猜结果: function fun(n,o) { console.log(o); return { fun:function(m){ return fun(m,n); } }; } 1. var a = fun(0); a.fun(1); a.fun(2); a.fun(3);//undefined,0,0,0 2. var b = fun(0).fun(1).fun(2).fun(3);//undefined,0,1,2, 3. var c = fun(0).fun(1); c.fun(2); c.fun(3);//undefined,0,1,1 ``` > [参考文章](https://segmentfault.com/a/1190000009886713) ## 元素获取 ### 原生 - getElementById() - getElementsByName() - getElementsByTagName() ### 模拟复杂元素获取方式 - getElementByClassNameAndTag 根据 `tagName` 和 `className`获取元素 ```javascript function getElementsByClassNameAndTag(tagName, className){ var tags = document.getElemetsByTagName(tagName); var resultTags = []; for(var i = 0; i < tags.length; i++){ if(tags[i].classname.indexOf(className) != -1){ /* important, indexOf */ resultTags[resultTags.length] = tags[i]; } } return resultTags; } ``` - getElementsByClassName 只根据 `className` 来获取元素 ```javascript function getElementsByClassName(className){ var resultTags = [] if( !document.getElementsByClassName ) { var allTags = document.getElementsByTagName('*'); for ( var i = 0; i < allTags.length; i++ ) { if( allTags[i].className.indexOf(className) != -1 ) { resultTags[resultTags.length] = allTags[i]; } } }else { var tags = document.getElementsByClassName(className); for ( var i = 0; i < tags.length; i++) { resultTags.push(tags[i]); } } return resultTags; } ```