JS学习笔记,持续记录
- JS笔记
- 2021-09-29
- 1172热度
- 0评论
事件源对象
回调函数内的this指向了dom对象本身,注意JQ的内存问题,删除dom时自定义的事件会继续存在。
$(".select li").click(function (event){
$(".select li").removeClass("current");
$(event.target).addClass("current");
})
$(".btn").click(function(e){
/* e 就是事件对象 */
e.target; /*事件的目标 dom*/
e.currentTarget; /*事件处理程序正在处理事件的那个元素*/
e.srcElement; /* 事件的目标 ie */
});
ES6/ES2015 模板字符串,代替复杂的字符串拼接
<script>
let a="我也不知道";
alert(`${a}是什么`)
</script>
JS细节知识总结
1. JavaScript 内部, 所有数字都是以64位浮点数形式储存, 即使整数也是如此。 这就是说, JavaScript 语言的底层根本没有整数, 所有数字都是小数( 64位浮点数) 。 容易造成混淆的是, 某些运算只有整数才能完成, 此时 JavaScript 会自动把64位浮点数, 转成32位整数, 然
后再进行运算。
2. 函数参数不是必需的, JavaScript 允许省略参数。 但是, 没有办法只省略靠前的参数, 而保留靠后的参数。 如果一定要省略靠前的参数, 只有显式传入 undefined 。 闭包:函数内部定义的函数,使用时作为函数的返回值传递到上层作用域(多个时使用数组、对象传递);
3. 函数外部无法读取函数内部声明的变量 ,函数内部可直接使用全局变量; 在 JavaScript 语言中, 只有函数内部的子函数才能读取内部变量, 因此可以把闭包简单理解成“定义在一个函数内部的函数”。 闭包最大的特点, 就是它可以“记住”诞生的环境, 比如 f2 记住了它诞生的环境 f1 , 所以从 f2 可以得到 f1 的内部变量。 在本质上, 闭包就是将函数内部和函数外部连接起来的一座桥梁。闭包的最大用处有两个, 一个是可以读取函数内部的变量, 另一个就是让这些变量始终保持在内存中,即闭包可以使得它诞生环境一直存在。 闭包使得内部变量记住上一次调用时的运算结果 ;
function createIncrementor(start) {
return function () {
return start++;
};
}
var inc = createIncrementor(5);
inc() /* 5 */
inc() /* 6 */
inc() /* 7 */
上面代码中, start 是函数 createIncrementor 的内部变量。 通过闭包, start 的状态被保留了, 每一次调用都是在上一次调用的基础上进行计算。 从中可以看到, 闭包 inc 使得函数 createIncrementor 的内部环境, 一直存在。 所以, 闭包可以看作是函数内部作用域的一个接口。为什么会这样呢? 原因就在于 inc 始终在内存中, 而 inc 的存在依赖于 createIncrementor ,因此也始终在内存中, 不会在调用结束后, 被垃圾回收机制回收。闭包的另一个用处, 是封装对象的私有属性和私有方法。
function Person(name) {
var _age;
function setAge(n) {
_age = n;
}
function getAge() {
return _age;
}
return {
name: name,
getAge: getAge,
setAge: setAge
};
}
var p1 = Person('张三');
p1.setAge(25);
p1.getAge() // 25
JS对象相关知识
1. 对象( object)是 JavaScript 语言的核心概念, 也是最重要的数据类型。对象就是一组“键值对”( key-value) 的集合, 是一种无序的复合数据集合 ; 如果键名是数值, 会被自动转为字符串。( ES6 引入了 Symbol 值也可以作为键名,键可以不加引号) , 对象的每一个键名又称为“属性”( property) , 它的“键值”可以是任何数据类型。 如果一个属性的值为函数, 通常把这个属性称为“方法”, 它可以像函数那样调用。
2. 对象采用大括号表示, 这导致了一个问题: 如果行首是一个大括号, 它到底是表达式还是语句?
{ foo: 123 } /*代码块*/
({ foo: 123 }) /*对象*/
为了避免这种歧义, JavaScript 引擎的做法是, 如果遇到这种情况, 无法确定是对象还是代码块,一律解释为代码块。 如果要解释为对象, 最好在大括号前加上圆括号。 因为圆括号的里面, 只能是表达式, 所以确保大括号只能解释为对象 。
3. 读取对象的属性, 有两种方法, 一种是使用点运算符, 还有一种是使用方括号运算符。 JavaScript 允许属性的“后绑定”, 也就是说, 你可以在任意时刻新增属性, 没必要在定义对象的时候, 就定义好属性。
var obj = {
p: 'Hello World'
};
obj.p /* "Hello World"*/
obj['p'] /* "Hello World"*/
a. 对象相关方法
- Object.keys(obj);查看一个对象本身的所有属性,返回对象键组成的数组。
- delete obj.p; 命令用于删除对象的属性, 删除成功后返回 true; delete 命令只能删除对象本身的属性;虽然 delete 命令删除继承的属性返回 true , 但该属性并不会被删除。
- in 运算符用于检查对象是否包含某个属性( 注意, 检查的是键名, 不是键值) , 如果包含就返回 true , 否则返回 false 。 它的左边是一个字符串, 表示属性名, 右边是一个对象; in 运算符的一个问题是, 它不能识别哪些属性是对象自身的, 哪些属性是继承的。 就像 对象 obj 本身并没有 toString 属性, 但是 in 运算符会返回 true , 因为这个属性是继承的 ;
- obj.hasOwnProperty(attr); 方法判断 attr是否为对象obj自身的属性。
6. JavaScript 语言的对象体系, 不是基于“类”的, 而是基于构造函数( constructor) 和原型链( prototype) 。JavaScript 语言使用构造函数( constructor) 作为对象的模板。 所谓”构造函数”, 就是专门用来生成实例对象的函数。 它就是对象的模板, 描述实例对象的基本结构。 一个构造函数, 可以生成多个实例对象, 这些实例对象都有相同的结构;
构造函数就是一个普通的函数, 但是有自己的特征和用法
var Vehicle = function () {
this.price = 1000;
};
上面代码中, Vehicle 就是构造函数。 为了与普通函数区别, 构造函数名字的第一个字母通常大写。
构造函数的特点有两个。函数体内部使用了 this 关键字, 代表了所要生成的对象实例。生成对象的时候, 必须使用 new 命令。
7. new 命令的作用, 就是执行构造函数, 返回一个实例对象。 使用 new 命令时, 根据需要, 构造函数也可以接受参数。
如果忘了使用 new 命令, 直接调用构造函数会发生什么事?
这种情况下, 构造函数就变成了普通函数, 并不会生成实例对象。 而且由于后面会说到的原因, this 这时代表全局对象, 将造成一些意想不到的结果。构造函数内部使用严格模式, 即第一行加上 use strict 。 这样的话, 一旦忘了使用 new 命令, 直接调用构造函数就会报错。
function Fubar(foo, bar){
'use strict';
this._foo = foo;
this._bar = bar;
}
Fubar()
/* TypeError: Cannot set property '_foo' of undefined*/
8. new 命令的原理
使用 new 命令时, 它后面的函数依次执行下面的步骤。
- 创建一个空对象, 作为将要返回的对象实例。
- 将这个空对象的原型, 指向构造函数的 prototype 属性。
- 将这个空对象赋值给函数内部的 this 关键字。
- 开始执行构造函数内部的代码。
也就是说, 构造函数内部, this 指的是一个新生成的空对象, 所有针对 this 的操在这个空对象上。 构造函数之所以叫“构造函数”, 就是说这个函数的目的, 就是操作一个( 即 this 对象) , 将其“构造”为需要的样子。如果构造函数内部有 return 语句, 而且 return 后面跟着一个对象, new 命令会回 return 语句指定的对象; 否则, 就会不管 return 语句, 返回 this 对象;
如果对普通函数( 内部没有 this 关键字的函数) 使用 new 命令, 则会返回一个空对象;
this 关键字
1. this 可以用在构造函数之中, 表示实例对象。 简单说, this 就是属性或方法“当前”所在的对象 ;
2.全局环境使用 this , 它指的就是顶层对象 window 。
3. 构造函数中的 this , 指的是实例对象。
4. 如果对象的方法里面包含 this , this 的指向就是方法运行时所在的对象。 该方法赋值给另一个对象, 就会改变 this 的指向。
对象原型
JavaScript 规定, 所有对象都有自己的原型对象( prototype) 。 一方面, 任何一个对象, 都可以充当其他对象的原型; 另一方面, 由于原型对象也是对象, 所以它也有自己的原型。 因此, 就会形成一个“原型链”( prototype chain) : 对象到原型, 再到原型的原型...
如果一层层地上溯, 所有对象的原型最终都可以上溯到 Object.prototype , 即 Object 构造函数的 prototype 属性(null)。
Object.getPrototypeOf(Obj)方法返回参数对象的原型
Object.getPrototypeOf(Object.prototype)
/* null*/
读取对象的某个属性时, JavaScript 引擎先寻找对象本身的属性, 如果找不到, 就到它的原型去找, 如果还是找不到, 就到原型的原型去找。 如果直到最顶层的 Object.prototype 还是找不到, 则返回 undefined 。 如果对象自身和它的原型, 都定义了一个同名属性, 那么优先读取对象自身的属性, 这叫做“覆盖”( overriding) 。注意, 一级级向上, 在整个原型链上寻找某个属性, 对性能是有影响的。 所寻找的属性在越上层的原型对象, 对性能的影响越大。 如果寻找某个不存在的属性, 将会遍历整个原型链。
如果让构造函数的 prototype 属性指向一个数组, 就意味着实例对象可以调用数组方法。
var MyArray = function () {};
MyArray.prototype = new Array();
var mine = new MyArray();
mine.push(1, 2, 3);
mine.length /* 3 */
mine instanceof Array /* true */
Object常用的函数
- Object.defineProperty(Obj,name,attr),为对象定义一个属性。
- Object.assign( target, source, source1 ) 方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。拷贝的属性是有限制的,只拷贝源对象的自身属性(不拷贝继承属性),也不拷贝不可枚举的属性(enumerable: false)。
- Object.getPrototypeOf/setPrototypeOf(Obj,Pro),设置和获取对象的原型对象
- Object.hasOwnProperty,获取对象是否具有指定的属性。
constructor 属性
prototype 对象有一个 constructor 属性, 默认指向 prototype 对象所在的构造函数。
function P() {}
P.prototype.constructor === P /* true */
- constructor 属性的作用是, 可以得知某个实例对象, 到底是哪一个构造函数产生的。
function F() {}; var f = new F(); f.constructor === F // true f.constructor === RegExp // false
- 有了 constructor 属性, 就可以从一个实例对象新建另一个实例。
function Constr() {} var x = new Constr(); var y = new x.constructor(); y instanceof Constr // true
上面代码中, x 是构造函数 Constr 的实例, 可以从 x.constructor 间接调用构造函数。 这使
得在实例方法中, 调用自身的构造函数成为可能。
细节总结
1. setTimeout无法直接像函数传递对象参数;
2. 被正则表达式捕获(匹配)到的字符串会被暂存起来,其中,由分组捕获到的字符串会从1开始编号,于是我们可以引用这些字符串:
var reg = /(d{4})-(d{2})-(d{2})/;
var dateStr = '2018-04-18';
reg.test(dateStr); //true
RegExp.$1 //2018
RegExp.$2 //04
RegExp.$3 //18
3. exec,正则匹配返回的是结果集。
3.贪婪匹配和非贪婪匹配:例如带匹配字符串 “123、123、123”;
/123(.*?)、/.exec("123、123、123"); #非贪婪匹配,123、
/123(.*?)、/.exec("123、123、123"); #贪婪匹配,123、123、
改变this的指向
改变this的指向,对于普通函数来说,正常调用的时候this等于window;
- call()、apply()、bind() 都是用来重定义 this 这个对象的!
- call 、bind 、 apply 这三个函数的第一个参数都是 this 的指向对象,call 的参数是直接放进去的,第二第三第 n 个参数全都用逗号分隔,直接放到后面 ,apply 的所有参数都必须放在一个数组里面传进去 ,bind 除了返回是函数以外,它 的参数和 call 一样。
obj.myFun.call(db,'成都','上海'); /* 德玛 年龄 99 来自 成都去往上海 */ obj.myFun.apply(db,['成都','上海']); /* 德玛 年龄 99 来自 成都去往上海 */ obj.myFun.bind(db,'成都','上海')(); /* 德玛 年龄 99 来自 成都去往上海 */ obj.myFun.bind(db,['成都','上海'])(); /* 德玛 年龄 99 来自 成都, 上海去往 undefined */
JS DOM高度
scrollHeight(文档内容实际高度,包括超出视窗的溢出部分)。
scrollTop(滚动条滚动距离)。
clientHeight(窗口可视范围高度)。
window.scrollTo(x,y),只能作用于window,不可作用于某一指定元素 。
scrollTop,作用某一指定元素时,生效的前提条件是:该指定元素的父盒子高度小于其高度
https://blog.csdn.net/fswan/article/details/17238933
闭包和普通函数
闭包指的是在函数内定义的函数,所以他能直接使用上一个函数内的所有数据对象,而普通函数被调用时,是无法使用上一个执行的函数的局部变量的。
import * as name from module
name 参数是“模块对象”的名称,它将用一种名称空间来引用导出。导出参数指定单个命名导出,而import * as name 语法导入所有导出
js中的&&和||
js的&&和||符号不同于PHP中的用法。
在PHP中&& 和|| 只会进行逻辑运算返回布尔值。
console.log( 5 && 4 );/*当结果为真时,返回第二个为真的值4*/
console.log( 0 && 4 );/*当结果为假时,返回第一个为假的值0*/
console.log( 5 || 4 );/*当结果为真时,返回第一个为真的值5*/
console.log( 0 || 0 );/*当结果为假时,返回第二个为假的值0*/
js中||和&&的特性帮我们精简了代码的同时,也带来了代码可读性的降低。
js中的self
self 指窗口本身,它返回的对象跟window对象是一模一样的。new一个普通函数的时候会调用这个函数。
Promise
Promise是会吞掉error的,因为promise的实现就在内部对所有error进行了捕获,且捕获到的error不是向外抛出(外指promise之外),而是沿着链找到最近的onreject回调传入,所以promise的错误处理只有两种办法
JS新发现
1. !!,!与undefined、null、空串的值都是true,!!则都是false。如果运行对象为true,!!结果还是true,免去了同时判断null、undefined、空串的复杂写法。
ES2020新特性js运算符 ?. 、?? 、??= 解释说明
- ?. 可选链运算符,let b=a?.name,只有当a存在,同时a具有name属性的时候,才会把值赋给b,否则就会将undefined赋值给b.重要的是,不管a存在与否,这么做都不会报错.
- ?? 空值合并运算符,仅在 左侧 是 nullish (null 或 undefined) 时,使用右侧的值
- ??= 逻辑空赋值运算符 (x ??= y) 仅在 x 是 nullish (null 或 undefined) 时对其赋值
URL.createObjectURL
URL.createObjectURL() 静态方法会创建一个 DOMString,其中包含一个表示参数中给出的对象的URL。这个 URL 的生命周期和创建它的窗口中的 document 绑定。这个新的URL 对象表示指定的 File 对象或 Blob 对象。
URL.createObjectURL(blob)和FileReader.readAsDataURL(file)很相似:
1.区别
通过FileReader.readAsDataURL(file)可以获取一段data:base64的字符串
通过URL.createObjectURL(blob)可以获取当前文件的一个内存URL
2.执行时机
createObjectURL是同步执行(立即的)
FileReader.readAsDataURL是异步执行(过一段时间)
3.内存使用
createObjectURL返回一段带hash的url,并且一直存储在内存中,直到document触发了unload事件(例如:document close)或者执行revokeObjectURL来释放。
FileReader.readAsDataURL则返回包含很多字符的base64,并会比blob url消耗更多内存,但是在不用的时候会自动从内存中清除(通过垃圾回收机制)
兼容性方面两个属性都兼容ie10以上的浏览器。
优劣对比:
使用createObjectURL可以节省性能并更快速,只不过需要在不使用的情况下手动释放内存
如果不太在意设备性能问题,并想获取图片的base64,则推荐使用FileReader.readAsDataURL
动画事件
- animationstart - CSS 动画开始后触发
- animationiteration - CSS 动画重复播放时触发
- animationend - CSS 动画完成后触发
问题记录
1.Promise
Promise内resolve之后,代码仍然会继续执行,所以也需要return(一直以为resolve可以作为return);
2.Object.assign
Object.assign( target, source, source1 ) 方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。拷贝的属性是有限制的,只拷贝源对象的自身属性(不拷贝继承属性),也不拷贝不可枚举的属性(enumerable: false)。
Object.assign(target, ...sources)
target--->目标对象
source--->源对象
retur:target,即目标对象
如果只是想将两个或多个对象的属性合并到一起,不改变原有对象的属性,可以用一个空的对象作为target对象。
Object.assign({},source);
如果有同名属性的话,后面的属性值会覆盖前面的属性值。Object.assign是浅拷贝;
3.判断对象是否具有某属性
property in Object
4.axios下载二进制文件
/* 开始请求 */
axios.get("/image", {responseType: 'arraybuffer'})
.then((res) => {
/*
* 判断请求结果
* */
let blob = new Blob([res.data], {type: "image/png"})
image.value = URL.createObjectURL(blob);
});
5.监听URL变化
HTML5的History接口,History对象是一个底层接口,不继承于任何的接口。History接口允许我们操作浏览器会话历史记录。
History的属性:
- History.length: 返回在会话历史中有多少条记录,包含了当前会话页面。此外如果打开一个新的Tab,那么这个length的值为1
- History.state:保存了会出发popState事件的方法,所传递过来的属性对象(后面会在pushState和replaceState方法中详细的介绍)
History方法:
- History.back(): 返回浏览器会话历史中的上一页,跟浏览器的回退按钮功能相同
- History.forward():指向浏览器会话历史中的下一页,跟浏览器的前进按钮相同
- History.go(): 可以跳转到浏览器会话历史中的指定的某一个记录页
- History.pushState():pushState可以将给定的数据压入到浏览器会话历史栈中,该方法接收3个参数,对象,title和一串url。pushState后会改变当前页面url,但是不会伴随着刷新
- History.replaceState():replaceState将当前的会话页面的url替换成指定的数据,replaceState后也会改变当前页面的url,但是也不会刷新页面。
pushState和repalce都会改变当前页面显示的url,但都不会刷新页面。pushState是压入浏览器的会话历史栈中,会使得History.length加1,而replaceState是替换当前的这条会话历史,因此不会增加History.length.
History.back()、History.forward()、History.go()事件是会触发popstate事件的,但是History.pushState()和History.replaceState()不会触发popstate事件。
var _wr = function(type) {
var orig = history[type];
return function() {
var rv = orig.apply(this, arguments);
var e = new Event(type);
e.arguments = arguments;
window.dispatchEvent(e);
return rv;
};
};
history.pushState = _wr('pushState');
history.replaceState = _wr('replaceState');
通过闭包拦截这两个方法,实现对相关事件的监听
Debugger
1.基于定时器
实现方法:
const check = () => {
const doCheck = (a) => {
(function() {}["constructor"]("debugger")());
doCheck(++a); // 递归调用
};
try {
doCheck(0);
} catch (err) {}
};
setInterval(check, 1000);
Debugger在控制台没打开的时候是无效的,所以每次定时器调用都会抛出超过最大调用栈的异常,然后中断。但是打开控制台之后Debugger就起作用了。无限递归调用Debugger。
破解方法:
_setInterval = setInterval
setInterval = function(a,b){
if(a.toString().indexOf('debugger') == -1){
return null;
}
_setInterval(a, b)
}