2020前端面试题及答案(2023前端面试题及答案整理(JavaScript))
JS类型
string ,number ,boolean ,undefined ,null ,symbol(es6) ,BigInt(es10) ,object
值类型和引用类型的区别
两种类型的区别是:存储位置不同;
值类型存储在栈(stack)中 ,占空间小 、大小固定 ,属于被频繁使用数据 ,所以放入栈中存储; 引用类型存储在堆(heap)中,占据空间大 、大小不固定 。如果存在栈中 ,影响程序运行性能;引用类型在栈中存储了指针 ,该指针指向堆中该实体的起始地址 。当解释器寻找引用值时,会首先检索其在栈中的地址 ,取得地址后从堆中获得实体 。JS的类型检测
typeof (判断一个变量是什么类型)undefined object function boolean string number symbol instanceof (判断当前对象是不是某个类型) <!-- 要检测的对象 instanceof 某个构造函数 --> function Car(make, model, year) { this.make = make; this.model = model; } var auto = new Car(Honda, Accord); console.log(auto instanceof Car); // expected output: true console.log(auto instanceof Object); // expected output: true Object.prototype.toString.call()(检测一个对象的类型) console.log(Object.prototype.toString.call("Lance"));//[object String]=== 和 == 的区别
== 在允许强制转换的条件下检查值的等价性 ,而 === 是在不允许强制转换的条件下检查值的等价性;
因此 === 常被称为「严格等价」 。(“55 ” == 55 true, “55 ” === 55 false 。p.s. 把字符串转为数值)
哪些非 boolean 值被强制转换为一个 boolean 时,它是 false ?
""(空字符串) 0, -0, NaN (非法的 number ) null, undefined{} 和 [] 的 valueOf 和 toString 的结果是什么?
{} 的 valueOf 结果为 {} ,toString 的结果为 “[object Object] ”
[] 的 valueOf 结果为 [] ,toString 的结果为 “ ”
null ,undefined 的区别?
null 表示一个对象是「没有值」的值 ,也就是值为 “空 ”;
undefined 表示一个变量声明了没有初始化(赋值);
undefined 的类型(typeof)是 undefined ;
null 的类型(typeof)是 object ;
JavaScript 将未赋值的变量默认值设为undefined;
JavaScript 从来不会将变量设为 null 。它是用来让程序员表明某个用 var 声明的变量时没有值的 。
在验证 null 时 ,一定要使用 === ,因为 == 无法分别 null 和 undefined
null == undefined // true null === undefined // falseDOM
DOM 操作——怎样添加 、移除 、移动 、复制 、创建和查找节点?
(1)创建新节点 createDocumentFragment() //创建一个DOM片段 createElement() //创建一个具体的元素 createTextNode() //创建一个文本节点 (2)添加 、移除 、替换 、插入 appendChild() removeChild() replaceChild() insertBefore() //在已有的子节点前插入一个新的子节点 (3)查找 querySelector("ul") / querySelectorAll("ul li") // 查找单个元素 / 多个元素 getElementsByTagName("div") getElementsByClassName() getElementById()DOM树操作
// 添加新节点 var p1 = document.createElement(p) p1.innerHTML = this is p1 div1.appendChild(p1) // 添加新创建的元素 // 移动已有节点 。注意 ,这里是“移动 ” ,并不是拷贝 var p2 = document.getElementById(p2) div1.appendChild(p2) // 获取父元素 var div1 = document.getElementById(div1) var parent = div1.parentElement // 获取子元素 var div1 = document.getElementById(div1) var child = div1.childNodes // 删除节点 var div1 = document.getElementById(div1) var child = div1.childNodes div1.removeChild(child[0])offsetWidth/offsetHeight ,clientWidth/clientHeight 与 scrollWidth/scrollHeight 的区别
offsetWidth/offsetHeight 返回值包含 content + padding + border ,效果与 e.getBoundingClientRect() 相同; clientWidth/clientHeight 返回值只包含 content + padding ,如果有滚动条 ,也不包含滚动条; scrollWidth/scrollHeight 返回值包含 content + padding + 溢出内容的尺寸 。什么是 Virtual DOM,为何要用 Virtual DOM?
Virtual DOM 的概念有很多解释 ,分别是:一个对象 ,两个前提,三个步骤 。
一个对象指的是 Virtual DOM 是一个基本的 JavaScript 对象 ,也是整个 Virtual DOM 树的基本 。
两个前提分别是 JavaScript 很快和直接操作 DOM 很慢 ,这是 Virtual DOM 得以实现的两个基本前提 。
得益于 V8 引擎的出现 ,让 JavaScript 可以高效地运行 ,在性能上有了极大的提高。
直接操作 DOM 的低效和 JavaScript 的高效相对比 ,为 Virtual DOM 的产生提供了大前提 。
三个步骤指的是 Virtual DOM 的三个重要步骤 ,分别是:生成 Virtual DOM 树 、对比两棵树的差异 、更新视图 。
1.生成 Virtual DOM 树:
DOM 是前端工程师最常接触的内容之一 ,一个 DOM 节点包含了很多的内容 ,但是抽象出一个 DOM 节点却只需要三部分:节点类型 ,节点属性 、子节点。所以围绕这三个部分 ,我们可以使用 JavaScript 简单地实现一棵 DOM 树 ,然后给节点实现渲染方法 ,就可以实现虚拟节点到真实 DOM 的转化 。
2.对比两棵树的差异:
比较两棵 DOM 树的差异是 Virtual DOM 算法最核心的部分,这也是我们常说的的 Virtual DOM 的 diff 算法 。在比较的过程中 ,我们只比较同级的节点 ,非同级的节点不在我们的比较范围内,这样既可以满足我们的需求 ,又可以简化算法实现 。
比较“树 ”的差异 ,首先是要对树进行遍历 ,常用的有两种遍历算法 ,分别是深度优先遍历和广度优先遍历 ,一般的 diff 算法中都采用的是深度优先遍历 。对新旧两棵树进行一次深度优先的遍历 ,这样每个节点都会有一个唯一的标记 。在遍历的时候 ,每遍历到一个节点就把该节点和新的树的同一个位置的节点进行对比 ,如果有差异的话就记录到一个对象里面 。
例如 ,上面的 div 和新的 div 有差异 ,当前的标记是 0 ,那么:patches[0] = [{difference}, {difference}, …] 。同理 p 是 patches[1] ,ul 是 patches[3],以此类推 。这样当遍历完整棵树的时候 ,就可以获得一个完整的差异对象 。
在这个差异对象中记录了有改变的节点 ,每一个发生改变的内容也不尽相同,但也是有迹可循 ,常见的差异包括四种 ,分别是:
替换节点 增加/删除子节点 修改节点属性 改变文本内容对象的原生方法
Object.assign()
copy 对象的可枚举属性
语法:Object.assign(target, …sources)
参数:目标对象, …源对象
返回值:目标对象
const obj = { a: 1 }; const copy = Object.assign({}, obj); console.log(copy); // { a: 1 }Object.create()
创建新对象
语法:Object.create(proto, [ propertiesObject ])
参数:新创建对象的原型对象, 用于指定创建对象的一些属性 ,(eg:是否可读、是否可写 ,是否可以枚举etc)
Object.is()
用来判断两个值是否是同一个值
Object.is(haorooms, haorooms); // true Object.is(window, window); // true Object.is(foo, bar); // false Object.is([], []); // false var test = { a: 1 }; Object.is(test, test); // true Object.is(null, null); // true // 特例 Object.is(0, -0); // false Object.is(-0, -0); // true Object.is(NaN, 0/0); // trueObject.keys / Object.values
返回给定对象的自身可枚举属性 / 值 的数组 ,数组中属性名的排列顺序和使用 for…in 循环遍历该对象时返回的顺序一致
// simple array var arr = [a, b, c]; console.log(Object.keys(arr)); // console: [0, 1, 2] // array like object var obj = { 0: a, 1: b, 2: c }; console.log(Object.keys(obj)); // console: [0, 1, 2] var obj = { foo: bar, baz: 42 }; console.log(Object.values(obj)); // [bar, 42] // array like object var obj = { 0: a, 1: b, 2: c }; console.log(Object.values(obj)); // [a, b, c] var obj = [e, s, 8]; // 等同于 { 0: e, 1: s, 2: 8 }; Object.values(obj); // [e, s, 8] //当把数字当做对象的键的时候 ,返回的数组以键的值升序排序 var obj = { 10: xxx, 1: yyy, 3: zzz }; Object.values(obj); // [yyy, zzz, xxx] Object.values(es8); // [e, s, 8]Object.entries()
Object.entries() 方法返回对象自身可枚举属性的键值对数组 ,其排列与使用 for...in 循环遍历该对象时返回的顺序一致(区别在于 for-in 循环也枚举原型链中的属性) 。
const obj = { foo: bar, baz: 42 }; console.log(Object.entries(obj)); // [ [foo, bar], [baz, 42] ] // iterate through key-value gracefully const obj = { a: 5, b: 7, c: 9 }; for (const [key, value] of Object.entries(obj)) { console.log(`${key}${value}`); // "a 5", "b 7", "c 9" } const obj2 = { 10: xxx, 1: yyy, 3: zzz }; Object.entries(obj2); // [[1, yyy], [3, zzz], [10, xxx]] Object.entries(es8); // [[0, e], [1, s], [2, 8]]数组
数组的遍历方法
标准for循环 forEach((当前值, 当前索引,当前数组)=>{}) 无法中途退出循环 ,只能用 return 退出本次回调 ,进行下一次回调 。 它总是返回 undefined 值,即使你 return 了一个值 。 for-in(不推荐)会把继承链的对象属性都会遍历一遍 ,而且数组遍历不一定按次序 for-in 循环返回的是所有能通过对象访问的 、可枚举的属性。 for (variable of iterable)(ES6)可迭代 Array ,Map ,Set,String 等(迭代的是值 value ) 在 for-of 中如果遍历中途要退出 ,可以使用 break 退出循环 。 ES5map (不改变原数组) 会给原数组中的每个元素都按顺序调用一次 callback 函数
reduce (不改变原数组) 数组中的前项和后项做某种计算,并累计最终值 。
// arr.reduce(function(total, currentValue, currentIndex, arr), initialValue) // callback 参数 // (累积器, 当前元素, 当前元素索引, 当前数组) // initialValue:指定第一次回调 的第一个参数 var wallets = [4, 7.8, 3] var totalMoney = wallets.reduce(function (countedMoney, curMoney) { return countedMoney + curMoney; }, 0)filter(不改变原数组)
var arr = [2, 3, 4, 5, 6] var morearr = arr.filter(function (number) { return number > 3 }) // [4,5,6]every(不改变原数组)测试数组的所有元素是否都通过了指定函数的测试
如果数组中检测到有一个元素不满足 ,则整个表达式返回 false ,且剩余的元素不会再进行检测。 如果所有元素都满足条件 ,则返回 true 。 var arr = [1,2,3,4,5] var result = arr.every(function (item, index) { return item > 2 }) // falsesome(不改变原数组)测试是否至少有一个元素通过 callback 中的条件.对于放在空数组上的任何条件 ,此方法返回 false 。
如果有一个元素满足条件 ,则表达式返回 true , 剩余的元素不会再执行检测 。 如果没有满足条件的元素 ,则返回 false 。 // some(callback, thisArg) // callback: // (当前元素, 当前索引, 调用some的数组) var arr = [1,2,3,4,5] var result = arr.some(function (item,index) { return item > 3 }) // true ES6find() & findIndex() 根据条件找到数组成员
find() 定义:用于找出第一个符合条件的数组成员 ,并返回该成员 ,如果没有符合条件的成员 ,则返回 undefined 。 findIndex() 定义:返回第一个符合条件的数组成员的位置 ,如果所有成员都不符合条件 ,则返回 -1 。 <!-- 语法 --> let new_array = arr.find(function(currentValue, index, arr), thisArg) let new_array = arr.findIndex(function(currentValue, index, arr), thisArg) <!-- 这两个方法都可以识别NaN,弥补了indexOf的不足 --> <!-- find --> let a = [1, 4, -5, 10].find((n) => n < 0); <!-- 返回元素-5 --> let b = [1, 4, -5, 10,NaN].find((n) => Object.is(NaN, n)); <!-- 返回元素NaN --> <!-- findIndex --> let a = [1, 4, -5, 10].findIndex((n) => n < 0); <!-- 返回索引2 --> let b = [1, 4, -5, 10,NaN].findIndex((n) => Object.is(NaN, n)); <!-- 返回索引4 -->keys() & values() & entries() 遍历键名 、遍历键值、遍历键名+键值
三个方法都返回一个新的 Array Iterator 对象 ,对象根据方法不同包含不同的值 。 // 语法 array.keys() array.values() array.entries() for (let index of [a, b].keys()) { console.log(index); } // 0 // 1 for (let elem of [a, b].values()) { console.log(elem); } // a // b for (let [index, elem] of [a, b].entries()) { console.log(index, elem); } // 0 "a" // 1 "b" for in 和 for of 区别 Object.prototype.objCustom = function() {}; Array.prototype.arrCustom = function() {}; let iterable = [3, 5, 7]; iterable.foo = hello; for (let i in iterable) { <-- 循环的是索引 console.log(i); // 打印 0, 1, 2, "foo", "arrCustom", "objCustom" } for (let i in iterable) { if (iterable.hasOwnProperty(i)) { console.log(i); // 打印 0, 1, 2, "foo" } } for (let i of iterable) { <-- 迭代的是值 console.log(i); // 打印 3, 5, 7 }JS数组有哪些方法
改变原数组的方法(9个) splice() 添加 / 删除数组元素splice() 方法向/从数组中添加/删除项目 ,然后返回被删除的项目
array.splice(index,howmany,item1,…,itemX)
index:必需 。整数 ,规定添加/删除项目的位置,使用负数可从数组结尾处规定位置 。 howmany:可选 。要删除的项目数量 。如果设置为 0 ,则不会删除项目 。 item1, …, itemX: 可选。向数组添加的新项目 。返回值: 如果有元素被删除,返回包含被删除项目的新数组 。
删除元素
// 从数组下标0开始 ,删除3个元素 let a = [1, 2, 3, 4, 5, 6, 7] let item = a.splice(0, 3) // [1,2,3] console.log(a) // [4,5,6,7] // 从最后一个元素开始删除3个元素,因为最后一个元素 ,所以只删除了7 let item = a.splice(-1, 3) // [7]删除并添加
// 从数组下标0开始 ,删除3个元素 ,并添加元素添加 let a = [1, 2, 3, 4, 5, 6, 7] let item = a.splice(0,3,添加) // [1,2,3] console.log(a) // [添加,4,5,6,7] // 从数组最后第二个元素开始 ,删除3个元素 ,并添加两个元素添加1 、添加2 let b = [1, 2, 3, 4, 5, 6, 7] let item = b.splice(-2,3,添加1,添加2) // [6,7] console.log(b) // [1,2,3,4,5,添加1,添加2]不删除只添加
let a = [1, 2, 3, 4, 5, 6, 7] let item = a.splice(0,0,添加1,添加2) // [] 没有删除元素 ,返回空数组 console.log(a) // [添加1,添加2,1,2,3,4,5,6,7] let b = [1, 2, 3, 4, 5, 6, 7] let item = b.splice(-1,0,添加1,添加2) // [] 没有删除元素 ,返回空数组 console.log(b) // [1,2,3,4,5,6,添加1,添加2,7] 在最后一个元素的前面添加两个元素 sort() 数组排序定义: sort() 方法对数组元素进行排序 ,并返回这个数组。
参数可选: 规定排序顺序的比较函数 。
默认情况下 sort() 方法没有传比较函数的话 ,默认按字母升序 ,如果不是元素不是字符串的话 ,会调用 toString() 方法将元素转化为字符串的 Unicode (万国码)位点 ,然后再比较字符 。
不传参
// 字符串排列 看起来很正常 var a = ["Banana", "Orange", "Apple", "Mango"] a.sort() // ["Apple","Banana","Mango","Orange"] // 数字排序的时候 因为转换成Unicode字符串之后,有些数字会比较大会排在后面 这显然不是我们想要的 var a = [10, 1, 3, 20,25,8] console.log(a.sort()) // [1,10,20,25,3,8];比较函数的两个参数:
sort 的比较函数有两个默认参数 ,要在函数中接收这两个参数 ,这两个参数是数组中两个要比较的元素,通常我们用 a 和 b 接收两个将要比较的元素:
若比较函数返回值 <0 ,那么 a 将排到 b 的前面; 若比较函数返回值 =0 ,那么 a 和 b 相对位置不变; 若比较函数返回值 >0 ,那么 b 排在 a 将的前面;数字升降序
var array = [10, 1, 3, 4, 20, 4, 25, 8]; array.sort(function(a,b){ return a-b; }); console.log(array); // [1,3,4,4,8,10,20,25]; array.sort(function(a,b){ return b-a; }); console.log(array); // [25,20,10,8,4,4,3,1]; pop() 删除一个数组中的最后的一个元素定义: pop() 方法删除一个数组中的最后的一个元素 ,并且返回这个元素 。
let a = [1,2,3] let item = a.pop() // 3 console.log(a) // [1,2] shift() 删除数组的第一个元素定义: shift()方法删除数组的第一个元素 ,并返回这个元素 。
let a = [1,2,3] let item = a.shift() // 1 console.log(a) // [2,3] push() 向数组的末尾添加元素定义:push() 方法可向数组的末尾添加一个或多个元素 ,并返回新的长度 。
参数: item1, item2, …, itemX ,要添加到数组末尾的元素
let a = [1,2,3] let item = a.push(末尾, 233) // 5 console.log(a) // [1,2,3,末尾, 233] unshift()定义:unshift() 方法可向数组的开头添加一个或更多元素 ,并返回新的长度 。
参数: item1, item2, …, itemX ,要添加到数组开头的元素
let a = [1, 2, 3] let item = a.unshift(开头, 开头2) // 5 console.log(a) // [ 开头, 开头2, 1, 2, 3 ] reverse() 颠倒数组中元素的顺序定义: reverse() 方法用于颠倒数组中元素的顺序 。
let a = [1,2,3] a.reverse() console.log(a) // [3,2,1] 不改变原数组的方法(8个) slice() 浅拷贝数组的元素定义: 方法返回一个从开始到结束(不包括结束)选择的数组的一部分浅拷贝到一个新数组对象 ,且原数组不会被修改 。
语法:array.slice(begin, end);
let a= [hello,world] let b=a.slice(0,1) // [hello] // 新数组是浅拷贝的 ,元素是简单数据类型 ,改变之后不会互相干扰 。 // 如果是复杂数据类型(对象,数组)的话 ,改变其中一个 ,另外一个也会改变 。 a[0] = 改变原数组 console.log(a,b) // [改变原数组,world] [hello] let a = [{name:OBKoro1}] let b = a.slice() console.log(b,a) // [{"name":"OBKoro1"}] [{"name":"OBKoro1"}] // a[0].name=改变原数组 // console.log(b,a) // [{"name":"改变原数组"}] [{"name":"改变原数组"}] join() 数组转字符串定义: join() 方法用于把数组中的所有元素通过指定的分隔符进行分隔放入一个字符串,返回生成的字符串 。
语法: array.join(str)
let a = [hello,world] let str = a.join() // hello,world let str2 = a.join(+) // hello+world let a = [[OBKoro1,23],test] let str1 = a.join() // OBKoro1,23,test let b = [{name:OBKoro1,age:23},test] let str2 = b.join() // [object Object],test // 对象转字符串推荐JSON.stringify(obj) // 结论: // join()/toString() 方法在数组元素是数组的时候 ,会将里面的数组也调用 join()/toString() , // 如果是对象的话 ,对象会被转为 [object Object] 字符串 。 toLocaleString() 数组转字符串定义: 返回一个表示数组元素的字符串。该字符串由数组中的每个元素的 toLocaleString() 返回值经调用 join() 方法连接(由逗号隔开)组成 。
let a = [{ name: OBKoro1 }, 23, abcd, new Date()] console.log(a.join(",")) console.log(a.toString()) console.log(a.toLocaleString(en-us)) console.log(a.toLocaleString(zh-cn)) [object Object],23,abcd,Tue Feb 26 2019 11:47:03 GMT+0800 (中国标准时间) [object Object],23,abcd,Tue Feb 26 2019 11:47:03 GMT+0800 (中国标准时间) [object Object],23,abcd,2/26/2019, 11:47:03 AM [object Object],23,abcd,2019/2/26 上午11:47:03 concat 合并数组定义: 方法用于合并两个或多个数组,返回一个新数组 。
语法:var newArr =oldArray.concat(arrayX,arrayX,…,arrayX)
参数:arrayX(必须):该参数可以是具体的值 ,也可以是数组对象。可以是任意多个 。
let a = [1, 2, 3] let b = [4, 5, 6] //连接两个数组 let newVal = a.concat(b) // [1,2,3,4,5,6] // 连接三个数组 let c = [7, 8, 9] let newVal2 = a.concat(b, c) // [1,2,3,4,5,6,7,8,9] // 添加元素 let newVal3 = a.concat(添加元素,b, c,再加一个) // [1,2,3,"添加元素",4,5,6,7,8,9,"再加一个"] // 合并嵌套数组 会浅拷贝嵌套数组 let d = [1,2 ] let f = [3,[4]] let newVal4 = d.concat(f) // [1,2,3,[4]] ES6扩展运算符 ... 合并数组 let a = [2, 3, 4, 5] let b = [ 4,...a, 4, 4] console.log(a,b) // [2, 3, 4, 5] [4,2,3,4,5,4,4] indexOf() 查找数组是否存在某个元素 ,返回下标定义: 返回在数组中可以找到一个给定元素的第一个索引 ,如果不存在 ,则返回-1 。
p.s. 字符串也有此方法 ,要注意当 ‘lance’.indexOf(‘’) 一个空字符串时 ,返回0而不是-1
语法:array.indexOf(searchElement,fromIndex)
参数:
searchElement (必须):被查找的元素
fromIndex (可选):开始查找的位置(不能大于等于数组的长度 ,返回-1) ,接受负值 ,默认值为0 。
严格相等的搜索:
数组的 indexOf 搜索跟字符串的indexOf不一样,数组的indexOf使用严格相等===搜索元素 ,即数组元素要完全匹配才能搜索成功 。
注意:indexOf() 不能识别NaN
let a=[啦啦,2,4,24,NaN] console.log(a.indexOf(啦)) // -1 console.log(a.indexOf(NaN)) // -1 console.log(a.indexOf(啦啦)) // 0 lastIndexOf() 查找指定元素在数组中的最后一个位置定义: 方法返回指定元素,在数组中的最后一个的索引 ,如果不存在则返回 -1 。(从数组后面往前查找)
语法:arr.lastIndexOf(searchElement,fromIndex)
参数:
searchElement(必须): 被查找的元素
fromIndex(可选): 逆向查找开始位置 ,默认值数组的 长度-1,即查找整个数组 。
关于fromIndex有三个规则:
正值 。如果该值大于或等于数组的长度 ,则整个数组会被查找 。 负值 。将其视为从数组末尾向前的偏移 。(比如-2 ,从数组最后第二个元素开始往前查找) 负值 。其绝对值大于数组长度,则方法返回 -1 ,即数组不会被查找 。 let a = [OB,4,Koro1,1,2,Koro1,3,4,5,Koro1] // 数组长度为10 // let b=a.lastIndexOf(Koro1,4) // 从下标4开始往前找 返回下标2 // let b=a.lastIndexOf(Koro1,100) // 大于或数组的长度 查找整个数组 返回9 // let b=a.lastIndexOf(Koro1,-11) // -1 数组不会被查找 let b = a.lastIndexOf(Koro1,-9) // 从第二个元素4往前查找 ,没有找到 返回-1 ES7 includes() 查找数组是否包含某个元素 返回布尔定义: 返回一个布尔值 ,表示某个数组是否包含给定的值
语法:array.includes(searchElement,fromIndex=0)
参数:
searchElement (必须):被查找的元素
fromIndex (可选):默认值为 0 ,参数表示搜索的起始位置 ,接受负值。正值超过数组长度 ,数组不会被搜索 ,返回 false 。负值绝对值超过长数组度 ,重置从 0 开始搜索 。
includes 方法是为了弥补 indexOf 方法的缺陷而出现的:
indexOf 方法不能识别 NaN indexOf 方法检查是否包含某个值不够语义化 ,需要判断是否不等于 -1 ,表达不够直观 let a = [OB,Koro1,1,NaN] // let b=a.includes(NaN) // true 识别NaN // let b=a.includes(Koro1,100) // false 超过数组长度 不搜索 // let b=a.includes(Koro1,-3) // true 从倒数第三个元素开始搜索 // let b=a.includes(Koro1,-100) // true 负值绝对值超过数组长度 ,搜索整个数组参考:js 数组详细操作方法及解析合集
字符串
字符串的方法
charAt 返回字符串字符从一个字符串中返回指定字符
如果指定的 index 值超出了该范围 ,则返回一个空字符串 var anyString = "Brave new world" console.log(anyString.charAt(0)) // B substring 返回字符串子集返回一个字符串在开始索引到结束索引之间的一个子集, 或从开始索引直到字符串的末尾的一个子集。
如果 indexStart 等于 indexEnd,substring 返回一个空字符串 。 如果省略 indexEnd ,substring 提取字符一直到字符串末尾 。 如果任一参数小于 0 或为 NaN ,则被当作 0 。 如果任一参数大于 stringName.length,则被当作 stringName.length 。 如果 indexStart 大于 indexEnd ,则 substring 的执行效果就像两个参数调换了一样 。见下面的例子 。 var anyString = "Mozilla" // 输出 "Moz" console.log(anyString.substring(0,3)) console.log(anyString.substring(3,0)) console.log(anyString.substring(3,-3)) console.log(anyString.substring(3,NaN)) console.log(anyString.substring(-2,3)) console.log(anyString.substring(NaN,3)) replace 替换字符串返回一个由替换值(replacement)替换一些或所有匹配的模式(pattern)后的新字符串 。模式可以是一个字符串或者一个 正则表达式 ,替换值可以是一个字符串或者一个每次匹配都要调用的回调函数 。
toLowerCase / toUpperCase 转换字母大小写字母转为全小写或全大写
includesincludes() 返回布尔值:表示是否找到了参数字符 。
let str = "qdywxs" console.log(str.includes("y")) //-->true repeat获取字符串重复 n 次 。
let s = "qdywxs" console.log(s.repeat(3)) //-->qdywxsqdywxsqdywxs startsWith返回布尔值:表示参数字符串是否在源字符串的头部 。
console.log("swlance".startsWith("s")); //-->true console.log("swlance".startsWith("l")); //-->false endsWith返回布尔值 ,表示参数字符串是否在源字符串的尾部 。
console.log("swlance".endsWith("e")); //-->true console.log("swlance".endsWith("o")); //-->false padStart/padEnd在 ES 8 中String新增了两个实例函数String.prototype.padStart和String.prototype.padEnd ,允许将空字符串或其他字符串添加到原始字符串的开头或结尾。
String.padStart(targetLength,[padString])
_targetLength:_当前字符串需要填充到的目标长度 。如果这个数值小于当前字符串的长度 ,则返回当前字符串本身 。padString:(可选)填充字符串。如果字符串太长 ,使填充后的字符串长度超过了目标长度 ,则只保留最左侧的部分 ,其他部分会被截断 ,此参数的缺省值为空格 。
String.padEnd(targetLength,padString]) 参数释义同上 。
es8.padStart(2); // es8 es8.padStart(5); // es8 es8.padStart(6, 1891); // 189es8 es8.padStart(14, coffe); // coffecoffeces8 es8.padStart(7, 0); // 0000es8 es8.padEnd(2); // es8 es8.padEnd(5); // es8 es8.padEnd(6, 1891); // es8189 es8.padEnd(14, coffe); // es8coffecoffec es8.padEnd(7, 9); // es89999什么是 JavaScript 作用链域?
全局函数无法查看局部函数的内部细节 ,但局部函数可以查看其上层的函数细节 ,直至全局细节 。
当需要从局部函数查找某一属性或方法时 ,如果当前作用域没有找到,就会上溯到上层作用域查找 , 直至全局函数 ,这种组织形式就是作用域链 。
闭包
什么是闭包/对闭包的理解
函数中有另一个函数或有另一个对象,里面的函数或者是对象都可以使用外面函数中定义的变量或者参数 ,此时形成闭包 。
YouDontKnowJS对闭包的解释 —— 闭包就是函数能够记住并访问它的词法作用域 ,即使当这个函数在它的词法作用域之外执行时 。由于这个性质 ,闭包让我们能够从一个函数内部访问其外部函数的作用域
闭包就是能够读取其他函数内部变量的函数 。可以简单理解成“定义在一个函数内部的函数 ”
闭包的用途
保存:缓存数据 ,延长作用域链 保护:形成私有作用域 ,保护里面私有变量不受外界干扰 ,避免全局污染缺点:耗内存 ,耗性能 ,函数中的变量不能及时释放
如何使用闭包
要使用闭包 ,只需要简单地将一个函数定义在另一个函数内部 ,并将它暴露出来 。要暴露一个函数 ,可以将它返回或者传给其他函数 。
内部函数将能够访问到外部函数作用域中的变量 ,即使外部函数已经执行完毕 。
想要缓存数据的时候就用闭包,把想要缓存的数据放在外层函数和内层函数的中间位置 。
闭包应用场景
li 节点的 onclick 事件都能正确的弹出当前被点击的 li 索引 <ul id="testUL"> <li> index = 0</li> <li> index = 1</li> <li> index = 2</li> <li> index = 3</li> </ul> <script type="text/javascript"> var nodes = document.getElementsByTagName("li") for (var i = 0; i < nodes.length; i += 1) { nodes[i].onclick = (function (i) { return function () { // <----重点是此处返回了个一个匿名函数 ,这个函数能访问 // 立即执行函数作用域内的i这个变量 ,形成闭包 console.log(i) } //不用闭包的话,值每次都是4 })(i) } </script> 用闭包模拟私有方法使用闭包定义能访问私有函数和私有变量的公有函数(也叫做模块模式)
var counter = (function() { var privateCounter = 0 function changeBy(val) { privateCounter += val } return { increment: function() { changeBy(1) }, decrement: function() { changeBy(-1) }, value: function() { return privateCounter; } } })() // 立即执行函数 console.log(counter.value()) // logs 0 counter.increment() counter.increment() console.log(counter.value()) // logs 2 counter.decrement() console.log(counter.value()) // logs 1环境中包含两个私有项:名为 privateCounter 的变量和名为 changeBy 的函数 。 它俩都无法在匿名函数外部直接访问。必须通过匿名包装器返回的对象的三个公共函数访问 。
this
对 this 的理解
this 总是指向函数的直接调用者(而非间接调用者) 如果有 new 关键字 ,this 指向 new 出来的那个对象 在事件中 ,this 指向触发这个事件的对象 ,特殊的是 ,IE 中的 attachEvent 中的 this 总是指向全局对象 window重要:
普通函数的 this 指向是在函数的执行期间绑定的 箭头函数的 this 指向是在函数创建期间就绑定好了的 ,指向的是创建该箭头函数所在的作用域对象 一般不在事件(比如 onclick )上传递箭头函数 ,使用 function 就好判定 this
摘自 YouDontKnowJS
现在 ,我们可以按照优先顺序来总结一下从函数调用的调用点来判定 this 的规则了 。按照这个顺序来问问题 ,然后在第一个规则适用的地方停下。
函数是通过 new 被调用的吗(new 绑定)?如果是 ,this 就是新构建的对象 。
var bar = new foo() 函数是通过 call 或 apply 被调用(明确绑定) ,甚至是隐藏在 bind 硬绑定 之中吗?如果是 ,this 就是那个被明确指定的对象 。
var bar = foo.call( obj2 ) 函数是通过环境对象(也称为拥有者或容器对象)被调用的吗(隐含绑定)?如果是 ,this 就是那个环境对象 。
var bar = obj1.foo() 否则,使用默认的 this(默认绑定) 。如果在 strict mode 下 ,就是 undefined ,否则是 global 对象 。
var bar = foo()以上,就是理解对于普通的函数调用来说的 this 绑定规则 所需的全部 。是的……几乎是全部 。
apply, call, bind 的区别
apply, call, bind 本身存在于大 Function 构造函数的 prototype 中
所有的函数都是大 Function 的实例对象
apply, call, bind 方法都可以改变 this 的指向
apply(对象, [参数1, 参数2, 餐数3, …]) call(对象, 参数1, 参数2, 餐数3,…) bind(对象,参数1, 参数2, 餐数3,…) 函数名称.bind()---->返回值是复制之后的这个函数区别
apply ,call 是调用的时候改变 this 指向 ,然后返回函数执行的结果 。 参数较多时用 apply ,参数较少时用 call bind 是复制一份函数并返回 ,并且这个函数的 this 指向变成了传入的第一个参数 。手写 bind 、apply 、call
call Function.prototype.call = function (context, ...args) { context = context || window; const fnSymbol = Symbol("fn"); context[fnSymbol] = this; context[fnSymbol](...args); delete context[fnSymbol]; } apply Function.prototype.apply = function (context, argsArr) { context = context || window; const fnSymbol = Symbol("fn"); context[fnSymbol] = this; context[fnSymbol](...argsArr); delete context[fnSymbol]; } bind Function.prototype.bind = function (context, ...args) { context = context || window; const fnSymbol = Symbol("fn"); context[fnSymbol] = this; return function (..._args) { _args = _args.concat(args); context[fnSymbol](..._args); delete context[fnSymbol]; } }JavaScript 原型
什么是原型
实例对象中有个属性 __proto__ ,是个对象 ,叫原型 ,不是标准的属性 ,浏览器使用的----->可以叫原型对象
构造函数中有一个属性 prototype ,也是个对象 ,叫原型 ,是标准属性 ,程序员使用—>可以叫原型对象
实例对象的 __proto__ 和构造函数中的 prototype 相等—> true
又因为实例对象是通过构造函数来创建的,构造函数中有原型对象prototype
实例对象的 __proto__ 指向了构造函数的原型对象 prototype
每个对象都会在其内部初始化一个属性 ,就是 prototype(原型) 。原型就是 __proto__(IE8不支持 ,非标准属性) 或者是 prototype ,都是原型对象 。
作用
共享数据 ,目的是:节省内存空间 实现继承 ,目的是:节省内存空间原型链
什么是原型链
当对象查找一个属性的时候 ,如果没有在自身找到 ,那么就会查找自身的原型 ,如果原型还没有找到 ,那么会继续查找原型的原型 ,直到找到 Object.prototype 的原型时 ,此时原型为 null ,查找停止 。这种通过 通过原型链接的逐级向上的查找链被称为原型链。
原型和原型链
原型链最终指向
分别使用原型链和 class 的方式实现继承
1. 组合继承(原型链 + 借用构造函数)【不推荐】 function Person(name, age) { this.name = name this.age = age } Person.prototype.sayHi=function () {} function Student(name, age, weight) { // 借用构造函数 Person.call(this, name, age) this.weight = weight } // 改变原型指向----继承 // 我们让 Student.prototype 指向一个 Person 的实例对象 // 这个对象的 __proto__ 指向的是 Person.prototype // 所以我们就可以借助这个实例对象拿到 sayHi 方法 ,实现继承 Student.prototype = new Person() Student.prototype.eat = function () {} var stu = new Student("Lance", 20, 120) var stu2 = new Student("Will", 200 , 110) // 属性和方法都被继承了由上面方案引出的问题:
为什么不能 Student.prototype = Person.prototype对象的赋值只是引用的赋值, 上面两者都指向同一个内存地址 ,只要随便通过 1 个途径就能修改该内存地址的对象 ,这样子类就无法单独扩展方法,因为会影响父类 。
单纯的原型链继承有什么缺陷虽然改变了原型的指向 ,但属性在初始化的时候就已经固定了【Student.prototype = new Person(“小明 ”, 29, 90)】 ,如果是多个对象实例化,那么每个实例对象的属性的初始值就都是一样的 。换句话说 ,无法向父类传递参数。
单纯的借用构造函数继承有什么缺陷只能继承父类构造函数里面的属性和方法【Person.call(this, name, age)】 ,但父类的 prototype(原型)上的属性和方法不能继承 。
组合继承的缺点调用了两次父类的构造函数 。第一次给子类的原型添加了父类构造函数中的属性方法 ,第二次又给子类的构造函数添加了父类的构造函数的属性方法 ,从而覆盖了子类原型中的同名参数 。这种被覆盖的情况造成了性能上的浪费:
function SuperType() { this.name = parent this.arr = [1, 2, 3] } SuperType.prototype.say = function() {} function SubType() { SuperType.call(this) // 第二次调用SuperType } SubType.prototype = new SuperType() // 第一次调用SuperType 2. 寄生组合继承【推荐】 function Person(name, age) { this.name = name this.age = age this.sayHi = function() {} } Person.prototype.eat = function() {} function Student(name, age, weight) { Person.call(this, name, age) this.weight = weight this.study = function() {} } var F = function (){} // 核心:因为是对父类原型的复制 ,所以不包含父类的构造函数 ,也就不会调用两次父类的构造函数造成浪费 F.prototype = Person.prototype // 创建了父类原型的浅复制 Student.prototype = new F(); // 上面三段js代码也可以用 Student.prototype = Object.create(Person.prototype) 代替 Student.prototype.constructor = Student // 修正原型的构造函数 var stu1 = new Student("Lance", 19, 120) console.dir(stu1) 3. class 实现继承 class Person { constructor(name, age) { this.name = name this.age = age // 类本身的方法 this.sayHi = function() {} } // 这里的 eat 相当于 prototype 中的 eat eat() {} } // 关键点:extends super class Student extends Person { constructor(name, age, weight) { super(name, age) this.weight = weight this.study = function() {} } run() {} } var stu = new Student("Jerry", 20, 100) console.dir(stu)原型链 ,proto 和 prototype 的区别
对象拥有 __proto__ 属性 ,函数拥有 prototype 属性 。某个实例对象的 __proto__ 指向构造它的构造函数的 prototype 属性 。所以:实例对象的 __proto__ 指向了构造函数的原型对象 prototype
: // 构造函数 function B(b) { this.b = b } // 实例对象 var b = new B(lc) b.__proto__ === B.prototype // true对象
函数(包括构造函数)是对象
对象不一定是函数
对象有 __proto__
函数有 prototype
创建对象的三种方式
字面量的方式 var obj = { name: "Lance", age: 20, sayHi: function(){} } 调用系统的构造函数 var obj = new Object() obj.name = Lance obj.age = 20 obj.sayHi = function(){} 自定义构造函数 function Person(name, age) { this.name = name this.age = age this.sayHi = function(){} } new 操作符具体干了什么呢? 创建一个新对象 ,如:var obj = {}; 新对象的 __proto__ 属性指向构造函数的原型对象 。 将构造函数的作用域赋值给新对象 。(也所以this对象指向新对象) 执行构造函数内部的代码 ,将属性添加给obj中的this对象 。 返回新对象obj 。 function _new(fn, ...args) { const obj = Object.create(fn.prototype) const ret = fn.apply(obj, args) return ret instanceof Object ? ret : obj } 通过 new 的方式创建对象和通过字面量创建有什么区别? 字面量创建对象 ,不会调用 Object 构造函数, 简洁且性能更好; new Object() 方式创建对象本质上是方法调用 ,涉及到在 proto 链中遍历该方法,当找到该方法后 ,又会生产方法调用必须的 堆栈信息 ,方法调用结束后,还要释放该堆栈 ,性能不如字面量的方式事件
什么是事件 ,IE与火狐的事件机制有什么区别? 如何阻止冒泡?
我们在网页中的某个操作(有的操作对应多个事件) 。例如:当我们点击一个按钮就会产生一个事件 。是可以被 JavaScript 侦测到的行为 。 事件处理机制:IE9以下只支持事件冒泡 、Firefox 、Chrome等则同时支持两种事件模型 ,也就是:捕获型事件和冒泡型事件。 ev.stopPropagation();(旧 ie 的方法 ev.cancelBubble = true)事件流
事件流是网页元素接收事件的顺序 ,"DOM2级事件"规定的事件流包括三个阶段:
事件捕获阶段 处于目标阶段 事件冒泡阶段首先发生的事件捕获 ,为截获事件提供机会 。
然后是实际的目标接受事件 。
最后一个阶段是时间冒泡阶段 ,可以在这个阶段对事件做出响应。
虽然捕获阶段在规范中规定不允许响应事件 ,但是实际上%E
创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!