JavaScript面试题
必看: https://juejin.im/post/5c6ad9fde51d453c356e37d1
# 1.写一个方法去掉字符串中的空格
function deleSpac(str,direction) { // 1 串的模板 2 清除哪边空格
let Reg = '';
switch(direction) {
case 'left' : // 去除左边
Reg = /^[\s]+/g;
break;
case 'right' : // 去除右边
Reg = /([\s]*)$/g;
break;
case 'both' : // 去除两边
Reg = /(^\s*)|(\s*$)/g
break;
default : // 没传默认全部,且为下去除中间空格做铺垫
Reg = /[\s]+/g;
break;
}
let newStr = str.replace(Reg,'');
if ( direction == 'middle' ){
let RegLeft = str.match(/(^\s*)/g)[0]; // 保存右边空格
let RegRight = str.match(/(\s*$)/g)[0]; // 保存左边空格
newStr = RegLeft + newStr + RegRight; // 将空格加给清完全部空格后的字符串
}
return newStr;
}
# 2.正则表达式详解
1>. 小括号():
匹配小括号内的字符串,可以是一个,也可以是多个,常跟“|”(或)符号搭配使用,是多选结构的
示例 1:string name = "way2014"; regex:(way|zgw) result:结果是可以匹配出 way 的,因为是多选结构,小括号是匹配字符串的
示例 2:string text = "123456789"; regex:(0-9) result:结果是什么都匹配不到的,它只匹配字符串"0-9"而不是匹配数字, [0-9]这个字符组才是匹配 0-9 的数字
2>.中括号[]:
匹配字符组内的字符,比如咱们常用的[0-9a-zA-Z.?!]等,在[]内的字符都是字符,不是元字符,比如“0-9”、“a-z”这中间的“-”就是连接符号,表示范围的元字符,如果写成[-!?(]这样的话,就是普通字符
示例 1: string text = "1234567890"; regex:[0-9] result:结果是可以匹配出字符串 text 内的任意数字了,像上边的【或符号“|”在字符组内就是一个普通字符】
示例 2:string text = "a|e|s|v"; regex:[a|e|s] result:结果就是匹配字符 a、e、|三个字符,这个跟(a|e|s)有区别的,区别就是(a|e|s)匹配的是 a、e、s 三个字符的随意一个,三个中的任意一个,这是的|是元字符
3>.大括号{}:
匹配次数,匹配在它之前表达式匹配出来的元素出现的次数,{n}出现 n 次、{n,}匹配最少出现 n 次、{n,m}匹配最少出现 n 次,最多出现 m 次
# 3.写一个方法把下划线命名转成大驼峰命名
function toCamelCase(str) {
if (typeof str !== 'string') {
return str;
}
return str
.split('_')
.map(item => item.charAt(0).toUpperCase() + item.substr(1, item.length))
.join('');
}
function toCamel(str) {
str = str.replace(/(\w)/, (match, $1) => `${$1.toUpperCase()}`)
**while(str.match(/\w_\w/)) {
str = str.replace(/(\w)(_)(\w)/, (match, $1, $2, $3) => `${$1}${$3.toUpperCase()}`)
}**
return str
}
console.log(toCamel('a_c_def')) // ACDef
# 4.写一个方法切换字符大小写
charCodeAt() 方法可返回指定位置的字符的 Unicode 编码
fromCharCode() 可接受一个指定的 Unicode 值,然后返回一个字符串
map()生成新数组 不改变原数组
function caseConvert(str) {
return str.split('').map(s => {
const code = s.charCodeAt();
// 检测是否非字母
if (code < 65 || code > 122 || code > 90 && code < 97) return s;
if (code <= 90) {
return String.fromCharCode(code + 32) //切换小写
} else {
return String.fromCharCode(code - 32) //切换大写
}
}).join('')
}
console.log(caseConvert('AbCdE')) // aBcDe
function caseConvertEasy(str) {
return str.split('').map(s => {
if (s.charCodeAt() <= 90) {
return s.toLowerCase()
}
return s.toUpperCase()
}).join('')
}
console.log(caseConvertEasy('AbCxYz')) // aBcXyZ
# 5.写一个去除制表符和换行符的方法
# 6.写一个判断数据类型的方法
function type (obj) {
return Object.prototype.toString.call(obj).replace(/\[object\s|\]/g,'');
}
console.log(type([])) //"Array"
console.log(type(1)) //"Number"
Object.prototype.toString.call()方法浅谈 - dataman - 博客园
# 7.JS 所有内置对象属性和方法汇总
# 8.自定义实现一个 assign 方法
//自定义一个assign方法
function copy(target){
if(target == null){
throwError('出错:Cannot convert undefined or null to object');
}
var target = new Object(target);
for(var i = 1;i < arguments.length;i ++){
var source = arguments[i];
for(var key in source){
if(source.hasOwnProperty(key)){
//若当前属性为源对象自有属性,则拷贝至目标对象
target[key] = source[key];
}
}
}
return target;
}
# 9.冒泡排序
const maopao = (arr) => {
for (let i = 0; i < arr.length - 1; i++) {
for (let j = 0; **j < arr.length - 1 - i**; j++) {
if (arr[j] >= arr[j + 1]) {
let temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
return arr
}
console.log(maopao([5, 3, 4, 10, 3, 4, 2, 0, 1]))
# 10.this 详解
默认绑定:
function foo() {
console.log( this.a );
}
var a = 2; foo(); // 2
//函数调用时应用了 this 的默认绑定,因此 this 指向全局对象
//如果使用严格模式(strict mode),则不能将全局对象用于默认绑定
隐式绑定(有上下文)
隐式丢失:
function foo() { console.log( this.a );
}
var obj = { a: 2,
foo: foo };
var bar = obj.foo; // 函数别名!
var a = "oops, global"; // a 是全局对象的属性 bar(); // "oops, global"
//虽然 bar 是 obj.foo 的一个引用,但是实际上,它引用的是 foo 函数本身,
//因此此时的 bar() 其实是一个不带任何修饰的函数调用,因此应用了默认绑定。
//更微妙的例子:
function foo() { console.log( this.a );
}
function doFoo(fn) {
// fn 其实引用的是 foo fn(); // <-- 调用位置!
}
var obj =
{
a: 2,
foo: foo
};
var a = "oops, global"; // a 是全局对象的属性 doFoo( obj.foo ); // "oops, global"
//传入setTimeout同理:
setTimeout( obj.foo, 100 ); // "oops, global"
显示绑定:
使用 call(..) 可以确保 this 指向函数对象 foo 本身
硬绑定:
function foo(something) { console.log( this.a, something );
return this.a + something;
}
// 简单的辅助绑定函数 function bind(fn, obj) {
return function() {
return fn.apply( obj, arguments );
};
}
var obj = { a:2};
var bar = bind( foo, obj );
var b = bar( 3 ); // 2 3 console.log( b ); // 5
//ES5 提供了内置的方法 Function.prototype.bind
new 绑定:
function foo(a) {
this.a = a;
}
var bar = new foo(2);
console.log( bar.a ); // 2
//使用 new 来调用 foo(..) 时,我们会构造一个新对象并把它绑定到 foo(..) 调用中的 this 上
箭头函数不使用 this 的四种标准规则,而是根据外层(函数或者全局)作用域来决 定 this
- 对于需要使用 object.method()方式调用的函数,使用普通函数定义,不要使用箭头函数。对象方法中所使用的 this 值有确定的含义,指的就是 object 本身。
- 其他情况下,全部使用箭头函数。
# 11.call,apply,bind
JavaScript 中 call()、apply()、bind() 的用法 | 菜鸟教程
// 我们也可以使用.bind(this)来规避this变来变去的问题
var circle = {
radius: 10,
outerDiameter() {
var innerDiameter = function() {
console.log(2 * this.radius);
};
**innerDiameter = innerDiameter.bind(this);
//该this位于outer函数中即把inner的this绑定到outer**
innerDiameter();
}
};
circle.outerDiameter(); //20
# 12.详解 JS 函数柯里化
# 14.复制对象
对于 JSON 安全(也就是说可以被序列化为一个 JSON 字符串并且可以根据这个字符串解 析出一个结构和值完全一样的对象)的对象来说,有一种巧妙的复制方法:
var newObj = JSON.parse( JSON.stringify( someObj ) );
ES6 定义了 Object.assign(..) 方法来实现浅复制,Object.assign(..) 方法的第一个参数是目标对象,之后还可以跟一个 或多个源对象。它会遍历一个或多个源对象的所有可枚举(enumerable,参见下面的代码) 的自有键(owned key,很快会介绍)并把它们复制(使用 = 操作符赋值)到目标对象,最 后返回目标对象,就像这样:
var newObj = Object.assign( {}, myObject );
newObj.a; // 2
newObj.b === anotherObject; // true
newObj.c === anotherArray; // true
newObj.d === anotherFunction; // true
# 15.对象 Descriptor 操作
# 1.禁止扩展:
(静默失效 并不会报错 访问时会抛出 undefined)
var myObject = { a:2 };
Object.preventExtensions( myObject );
myObject.b = 3;
myObject.b; // undefined
# 2.密封
Object.seal(..) 会创建一个“密封”的对象,这个方法实际上会在一个现有对象上调用 Object.preventExtensions(..) 并把所有现有属性标记为 configurable:false。
所以,密封之后不仅不能添加新属性,也不能重新配置或者删除任何现有属性(虽然可以 修改属性的值)。
# 3.冻结
Object.freeze(..) 会创建一个冻结对象,这个方法实际上会在一个现有对象上调用 Object.seal(..) 并把所有“数据访问”属性标记为 writable:false,这样就无法修改它们 的值。
# 16.ES6 拓展运算符...
# 17.Object 手写 Symbol.iterator
let obj = {
0: 'a',
1: 'b',
2: 'c',
}
Object.defineProperty(obj,Symbol.iterator,{
value:function(){
var o = this; // 保存this 指向被迭代对象
var index = 0;
var keys = Object.keys(o);
return {
next:function(){
return {
value:o[keys[index++]],
done:(index > keys.length)
}
}
}
}
})
console.log([...obj]);
# 18.显式多态
Vehicle.drive.call( this )
//如果直接执行 Vehicle.drive(),函数调用中的 this 会被绑定到 Vehicle 对象而不是 Car 对象
//这并不是我们想要的。因此,我们会使用 .call(this)来确保 drive() 在 Car 对象的上下文中执行
# 19.手写实现 hash 路由
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet" href="./style.css">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<a href="#/">/</a>
<a href="#/a">/a</a>
<a href="#/b">/b</a>
<button id='back'>back</button>
<script src="./test.js"></script>
</body>
</html>
const log = obj => {
console.log(obj);
};
const typelog = obj => {
console.log(typeof obj);
};
class Routers {
constructor(){
this.routes = {};
this.currentUrl = '';
this.history = [];
this.index = this.history.length - 1;
this.refresh = this.refresh.bind(this)
this.back = this.back.bind(this)
this.isBack = false;
window.addEventListener('load', this.refresh,false);
window.addEventListener('hashchange', this.refresh, false);
}
route(path,callback){
this.routes[path] = callback || function(){}
}
refresh(){
// hash 属性是一个可读可写的字符串,该字符串是 URL 的锚部分( 从 # 号开始的部分 )
if (!this.isBack){
this.currentUrl = location.hash.slice(1) || '/';
this.history.push(this.currentUrl);
this.index ++;
this.routes[this.currentUrl]();
}
}
back(){
this.isBack = true;
this.index <= 0 ? this.index = 0 : this.index -= 1;
location.hash = `#${this.history[this.index]}`
this.routes[this.history[this.index]]();
}
}
window.Router = new Routers();
const backButton = document.getElementById('back')
Router.route('/',()=>{
log('now is : /');
log(Router.history)
log(Router.index)
})
Router.route('/a',()=>{
log('now is : /a');
log(Router.history)
log(Router.index)
})
Router.route('/b',() => {
log('now is : /b');
log(Router.history)
log(Router.index)
})
backButton.addEventListener('click',Router.back,false)
# 20.设计模式举例
// 工厂模式:不暴露创建对象的具体逻辑 而是将将逻辑封装在一个函数中
class SampleFactory {
constructor(person){
this.name = person.name;
this.age = person.age;
}
static createPerson(personName,personAge){
return new SampleFactory({
'name':personName,
'age':personAge
})
}
showPerson(){
console.log(`${this.name} is ${this.age} years old.`)
}
}
const john = SampleFactory.createPerson('john',27);
john.showPerson(); // john is 27 years old.
//建造者模式:分步创建一个复杂的对象
function Car(){
this.wheel = '';
this.engine = '';
}
function Builder(){
this.finishCar = () => {
var car = new Car();
car.wheel = 'builded'
car.engine = 'builded'
return car
}
}
var builder = new Builder();
var car = builder.finishCar();
console.log(car);// Car {wheel: "builded", engine: "builded"}
// 适配器模式 : 用来解决两个接口不兼容问题
class Origin {
show(){
return 'Origin'
}
}
class Adapter {
constructor(){
this.adapter = new Origin();
}
show(){
console.log('i am Origin\'s Adapter')
}
}
const ad = new Adapter;
ad.show(); // i am Origin's Adapter
// 装饰器模式 : 不改变自身的基础上添加新功能
class Hero {
jump(){
console.log('the hero can jump!')
}
}
var myHero = new Hero();
myHero.fly = function fly(){
console.log('the hero can fly!')
}
myHero.fly();//the hero can fly!
myHero.jump();//the hero can jump!
# 21.理解模块机制 (ES6 之前)
var MyModules = (function Manager(){
var modules = {};
function define(name,deps,impl){
for(var i =0;i<deps.length; i++){
deps[i] = modules[deps[i]] // 传入['bar'] -> 找到moduels中bar对应的hello方法 -> 给回deps数组 -> 数组内容分配给impl的参数
}
modules[name] = impl.apply(impl,deps) //将未来定义的impl方法的this绑定到自身,传参绑定到deps
// modules是{ name : function }的键值对
log(modules)
}
function get(name){
return modules[name]
}
return {
define:define,
get:get
}
})();
MyModules.define('bar',[],function(){
function hello(who){
return 'let me introduce : ' + who;
}
return{
hello:hello
}
})
MyModules.define('foo',['bar'],function(bar){
var hungry = 'hippo';
function awesome(){
log(bar.hello(hungry).toUpperCase())
}
return{
awesome:awesome
}
})
var bar = MyModules.get('bar');
var foo = MyModules.get('foo')
log(bar.hello('hippo'))
foo.awesome()
# 22. Promise.all
Promise.all 可以将多个 Promise 实例包装成一个新的 Promise 实例。同时,成功和失败的返回值是不同的,成功的时候返回的是一个结果数组,而失败的时候则返回最先被 reject 失败状态的值。
let p1 = new Promise((resolve, reject) => {
resolve('成功了')
})
let p2 = new Promise((resolve, reject) => {
resolve('success')
})
let p3 = Promse.reject('失败')
Promise.all([p1, p2]).then((result) => {
console.log(result) //['成功了', 'success']
}).catch((error) => {
console.log(error)
})
Promise.all([p1,p3,p2]).then((result) => {
console.log(result)
}).catch((error) => {
console.log(error) // 失败了,打出 '失败'
})
# 23.Promise.race
顾名思义,Promse.race 就是赛跑的意思,意思就是说,Promise.race([p1, p2, p3])里面哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态。
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success')
},1000)
})
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('failed')
}, 500)
})
Promise.race([p1, p2]).then((result) => {
console.log(result)
}).catch((error) => {
console.log(error) // 打开的是 'failed'
})
# 24. 通过 Generator 让原生对象可迭代
方法一: 将 Generator 函数加到对象的 Symbol.iterator 属性上面
function* objectEntries() {
let propKeys = Object.keys(this);
//返回一个由一个给定对象的自身可枚举属性组成的数组
//如果对象的键-值都不可枚举,那么将返回由键组成的数组
for (let propKey of propKeys) {
yield [propKey, this[propKey]]; //以[key,value]数组形式返回
}
}
let jane = { first: 'Jane', last: 'Doe' };
jane[Symbol.iterator] = objectEntries; // 不加() !!!
for (let [key, value] of jane) {
console.log(`${key}: ${value}`);
}
// first: Jane
// last: Doe
方法二:通过 Generator 函数 objectEntries 为它加上遍历器接口
function* objectEntries(obj) {
let propKeys = Reflect.ownKeys(obj); // 是ownKeys 不是keys
// 返回一个由目标对象自身的属性键组成的数组。
for (let propKey of propKeys) {
yield [propKey, obj[propKey]];
}
}
let jane = { first: 'Jane', last: 'Doe' };
for (let [key, value] of objectEntries(jane)) {
console.log(`${key}: ${value}`);
}
// first: Jane
// last: Doe
# 25. next 方法的参数
function* foo(x) {
var y = 2 * (yield (x + 1));
var z = yield (y / 3);
return (x + y + z);
}
var a = foo(5);
a.next() // Object{value:6, done:false}
a.next() // Object{value:NaN, done:false}
a.next() // Object{value:NaN, done:true}
var b = foo(5);
b.next() // { value:6, done:false }
b.next(12) // { value:8, done:false }
b.next(13) // { value:42, done:true }
注意,由于 next 方法的参数表示上一个 yield 表达式的返回值,所以在第一次使用 next 方法时,传递参数是无效的。V8 引擎直接忽略第一次使用 next 方法时的参数,只有从第二次使用 next 方法开始,参数才是有效的。从语义上讲,第一个 next 方法用来启动遍历器对象,所以不用带有参数。
// wrapper返还一个普通函数 该普通函数返还一个用过第一次next的Generator函数
function wrapper(generatorFunction) {
return function (...args) {
let generatorObject = generatorFunction(...args);
generatorObject.next();
return generatorObject;
};
}
const wrapped = wrapper(function* () {
console.log(`First input: ${yield}`);
return 'DONE';
});
wrapped().next('hello!')
// First input: hello!
# 26. next()、throw()、return() 的共同点
next()、throw()、return()这三个方法本质上是同一件事,可以放在一起理解。它们的作用都是让 Generator 函数恢复执行,并且使用不同的语句替换 yield 表达式。
const g = function* (x, y) {
let result = yield x + y;
return result;
};
const gen = g(1, 2);
gen.next(); // Object {value: 3, done: false}
// next()是将yield表达式替换成一个值。
gen.next(1); // Object {value: 1, done: true}
// 相当于将 let result = yield x + y
// 替换成 let result = 1;
// throw()是将yield表达式替换成一个throw语句。
gen.throw(new Error('出错了')); // Uncaught Error: 出错了
// 相当于将 let result = yield x + y
// 替换成 let result = throw(new Error('出错了'));
// return()是将yield表达式替换成一个return语句。
gen.return(2); // Object {value: 2, done: true}
// 相当于将 let result = yield x + y
// 替换成 let result = return 2;
# 27. yield*
function* bar() {
yield 'x';
yield* foo();
yield 'y';
}
// 等同于
function* bar() {
yield 'x';
yield 'a';
yield 'b';
yield 'y';
}
// 等同于
function* bar() {
yield 'x';
for (let v of foo()) {
yield v;
}
yield 'y';
}
for (let v of bar()){
console.log(v);
}
// "x"
// "a"
// "b"
// "y"
yield*命令可以很方便地取出嵌套数组的所有成员。
function* iterTree(tree) {
if (Array.isArray(tree)) {
for(let i=0; i < tree.length; i++) {
yield* iterTree(tree[i]);
}
} else {
yield tree;
}
}
const tree = [ 'a', ['b', 'c'], ['d', 'e'] ];
for(let x of iterTree(tree)) {
console.log(x);
}
// a
// b
// c
// d
// e
# 28. Generator 实现状态机
var clock = function* () {
while (true) {
console.log('Tick!');
yield;
console.log('Tock!');
yield;
}
};
# 29. Generator 封装异步任务
var fetch = require('node-fetch');
function* gen(){
var url = 'https://api.github.com/users/github';
var result = yield fetch(url);
console.log(result.bio);
}var fetch = require('node-fetch');
function* gen(){
var url = 'https://api.github.com/users/github';
var result = yield fetch(url);
console.log(result.bio);
}
//使用
var g = gen();
var result = g.next();
result.value.then(function(data){
return data.json();
}).then(function(data){
g.next(data);
});
上面代码中,首先执行 Generator 函数,获取遍历器对象,然后使用 next 方法(第二行),执行异步任务的第一阶段。由于 Fetch 模块返回的是一个 Promise 对象,因此要用 then 方法调用下一个 next 方法。
# 30. async
# 31. 立即执行匿名函数
javascript 的 "!function" 是什么意思?_百度知道
这样写会报错:function(){alert(1)}()
因为 function 前面没有(或者! ~之类的运算符,js 解析器会试图将关键字 function 解析成函数声明语句,而不是函数定义表达式。
# 32. 继承
# 33. JS 几条基本规范
1、不要在同一行声明多个变量 2、请使用===/!==来比较 true/false 或者数值 3、使用对象字面量替代 new Array 这种形式 4、不要使用全局变量 5、Switch 语句必须带有 default 分支 6、函数不应该有时候有返回值,有时候没有返回值 7、For 循环必须使用大括号 8、IF 语句必须使用大括号 9、for-in 循环中的变量 应该使用 var 关键字明确限定作用域,从而避免作用域污染
# 34. JS 引用方法
- 行内引入
<body>
<input type="button" onclick="alert('行内引入')" value="按钮" />
<button onclick="alert(123)">点击我</button>
</body>
2
3
4
- 内部引入
<script>
window.onload = function() {
alert("js 内部引入!");
};
</script>
2
3
4
5
- 外部引入
<body>
<div></div>
<script type="text/javascript" src="./js/index.js"></script>
</body>
2
3
4
- 注意
1,不推荐写行内或者 HTML 中插入<script>
,因为浏览器解析顺序缘故,如果解析到死循环之类的 JS 代码,会卡住页面
2,建议在 onload 事件之后,即等 HTML、CSS 渲染完毕再执行代码
# 35. JS 的基本数据类型
Undefined、Null、Boolean、Number、String、新增:Symbol
# 36. 数组遍历
# forEach()
对数组中的每一项运行给定函数,这个方法没有返回值
不会改变原数组
/*item为当前遍历到的项,和arr[i]一样*/
var arr=sporter.forEach(function (item){item.sex='男'})
# Map()
对数组中每一项运行给定函数。返回每次函数调用的结果组成的数组
/*item为当前遍历到的项,和arr[i]一样*/
arr=arr.map(function(item){return item*2});
# Filter()
对数组中的每一项运行给定函数。返回该函数会返回 true 的项组成的数组
/*item为当前遍历到的项,和arr[i]一样*/
var arr=sporter.filter(function (item){return item.isHell})
# Every() / Some()
every()
对数组中的每一项运行给定函数,如果该函数对每一项都返回 true,则返回 true ;
/*item为当前遍历到的项,和sporter[i]一样*/
var arr=sporter.every(function (item){return item.isHell})
some()
对数组中的每一项运行给定函数,如果该函数对任一项返回 true,则返回 true;
/*item为当前遍历到的项,和sporter[i]一样*/
var arr=sporter.some(function (item){return item.isHell})
# Reduce()
reduce()
每次只能接受两个参数,我对着两个参数的理解就是,当前结果,和当前序列的下一项。作用效果和原理就是[x1, x2, x3, x4].reduce(f) = f(f(f(x1, x2), x3), x4)
。这个方法一般用在累计累加上,实用技巧暂时还没发现。比如,数字数组求和,字符串数组连接上。
var arr = ["字", "符", "串", "数", "组", "连", "接"];
var sum = arr.reduce((a, b) => {
return a + b;
});
2
3
4
# find() / findIndex()
find:方法返回传入一个测试条件(函数)符合条件的数组第一个元素。
findIndex:方法返回传入一个测试条件(函数)符合条件的数组第一个元素位置。
当数组中的元素在测试条件时返回 true 时, find 和 findIndex 返回符合条件的元素或者元素的索引位置,之后的值不会再调用执行函数。如果没有符合条件的元素返回 -1。
arr.find(function(val){return val>30})
# 37. 数组普通操作
# Array.push(item,item1,item2...)
- 作用:末尾添加元素
- 参数:可以是多个参数,也可以是数组变量
- 返回值:新数组的长度
- 注意:如果 push 一个数组变量时,新数组的数组长度只会+1; 旧数组会根据增加的数据变为多维数组。
//原数组:
const arr = [1, 2];
const item = [3, "4"];
//新数组:只返回新数组的长度
const newPush = arr.push(item);
console.log(arr); //[1, 2, [3, '4']]
console.log(newPush); //3
2
3
4
5
6
7
# Array.pop()
- 作用:删除元素
- 参数:该方法不接收任何参数
- 返回值:删除的元素
- 注意:删除的是最后一个元素
# Array.unshift()
- 作用:添加元素
- 参数:该方法不接收任何参数
- 返回值:新数组长度
- 注意:添加元素在数组第一位
# Array.shift()
- 作用:删除元素
- 参数:该方法不接收任何参数
- 返回值:删除的元素
- 注意:删除的是第一个元素
# Array.splice()
- 作用:删除 或 添加 或 删除同时添加
- 参数:起始下标,删除长度,要添加的元素 1,2,3...
- 返回值:删除元素
- 注意:当第二个参数为 0 时只向旧数组添加元素
//原数组
const arr = [4, 5, 6, 7, 1, 7];
//新数组
const arrSplice = arr.splice(-2, 2, 9, 8);
console.log(arrSplice); //[1,7]
console.log(arr); //[4, 5, 6, 7, 9, 8]
2
3
4
5
6
# Array.join()
- 作用:在数组的每一个元素后面添加参数;默认是用逗号做为分隔符
- 参数:接受用来添加在每个元素后
- 返回值:字符串
- 注意:join()/toString()方法在数组元素是数组的时候,会将里面的数组也调用 join()/toString();多维数组扁平化最快处理办法;如果是对象的话,对象会被转为[object Object]字符串。
//eg1:
const arr = [4, 5, 6, 7, 1, 7];
const arrJoin = arr.join();
console.log(arr, arrJoin); //[4, 5, 6, 7, 1, 7] "4,5,6,7,1,7"
// eg2: 多维数组扁平化(挺好用)
let a = [["liu", "23", [1, 2, ["21", "13"]]], "test"];
let str1 = a.join();
console.log(str1); //liu,23,1,2,21,13,test
// eg3: 为对象数组时
let b = [{ name: "liu", age: "23" }];
let str2 = b.join();
console.log(JSON.stringify(str2)); // [object Object],test
// 对象转字符串推荐 JSON.stringify(obj);只是改变了序列化而已,并不能还原数组
2
3
4
5
6
7
8
9
10
11
12
13
# Array.toLocaleString()
- 作用:数组转字符串
- 参数:无
- 返回值:字符串
- 注意:1.toLocaleString()还是调用的 join()方法 2.调用数组的 toLocaleString 方法,数组中的每个元素都会调用自身的 toLocaleString 方法, 3.对象调用对象的 toLocaleString,Date 调用 Date 的 toLocaleString
//eg1:
const arr = [4, 5, 6, 7, 1, 7];
const arrToLocaleString = arr.toLocaleString();
const arrJoin = arr.join(); //和 join 的作用一样,都是数组转字符串
console.log(arr, arrJoin, arrToLocaleString); //[4, 5, 6, 7, 1, 7] "4,5,6,7,1,7" "4,5,6,7,1,7"
//eg2:
let a = [{ name: "OBKoro1" }, 23, "abcd", new Date()];
let str = a.toLocaleString();
console.log(str, "--str--"); //[object Object],23,abcd,2019/4/11 下午 10:01:08
2
3
4
5
6
7
8
9
# Array.toString()
- 作用:数组转字符串
- 参数:无
- 返回值:字符串
- 注意:1.和 join()方法效果一样,但是没有优势。因为不能自定义分割 2.当数组和字符串操作的时候,js 会调用这个方法将数组自动转换成字符串 3.数组的元素之间依然有逗号分割,最后一个元素会与数组外面所有的字符串连为一体。
# Array.concat()
- 作用:拼接数组
- 参数:接收多个数组参数,也可以是具体的值
- 返回值:拼接后的数组
//eg1:
const arr = [4, 5, 6, 7, 1, 7];
const arr1 = "e";
const arr2 = [{ name: "liu" }, 1];
const arrConcet = arr.concat(arr2, arr1);
console.log(arrConcet); //[4, 5, 6, 7, 1, 7, {name: 'liu'}, 1, "e"]
2
3
4
5
6
# ...展开运算符
- 作用:展开数组 对象
//字面量数组构造或字符串:
const arr = [4, 5, 6, 7, 1, 7];
const arr1 = [...arr, "a"];
console.log(arr1, "---展开运算符---");
//eg2: 这里提一点:ECMAScript 2018规范新增特性
const obj = { name: "liu", sex: "男" };
const obj1 = { ...obj, age: 20 };
console.log(obj1, "---obj1---");
//eg3:
function myFunction(x, y, z) {
console.log(x, y, z, "---xyz---");
}
var args = [0, 1, 2];
myFunction(args); //[0, 1, 2] undefined undefined
//apply 语法:func.apply(thisArg, [argsArray]);这里 thisArg 为 null, [0,1,2]传值给[argsArray]
myFunction.apply(null, args); //0,1,2
myFunction(...args); //0,1,2 可以看出,使用了展开运算符更加简洁
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Array.indexOf()
- 作用:查找数组是否存在某个元素,返回下标;不存在则返回-1
- 参数:元素
- 返回值:下标 或 -1
- 注意:是严格匹配“ === ” ; 不识别 NaN; 识别 undefined; 字符串类型的 indexOf 是可以找到的
//eg1:
const arr = [4, 5, 6, 7, 1, 7];
const arr1 = ["aa", 1, 3, NaN, undefined];
console.log(arr1.indexOf("a")); //-1
console.log(arr1.indexOf(NaN)); //-1
console.log(arr1.indexOf(undefined)); //4
//eg2:
const str = "qwe";
console.log(str.indexOf("d")); //-1
2
3
4
5
6
7
8
9
# Array.lastIndexOf()
- 作用:逆向查找
- 参数:2 个:要查找的元素,要从下标几向前查找
- 返回值:下标
- 注意:是向前查找 向前!
//eg1:
const arr = [4, 5, 6, 7, 1, 7];
console.log(arr.lastIndexOf(7, -1)); //5
let a = ["a", 4, "b", 1, 2, "b", 3, 4, 5, "b"]; // 数组长度为 10
let b = a.lastIndexOf("b", 4); // 从下标 4 开始往前找 返回下标 2
let b1 = a.lastIndexOf("b", 100); // 大于或数组的长度 查找整个数组 返回 9
let b2 = a.lastIndexOf("b", -11); // -1 数组不会被查找
let b3 = a.lastIndexOf("b", -9); // 从第二个元素 4 往前查找,没有找到 返回-1
console.log(b, b1, b2, b3); //2,9,-1,-1
2
3
4
5
6
7
8
9
# Array.includes()
- 作用:判断数组是否包含某个值
- 参数:2 个:第一个参数是要查找的元素(可以是变量),第二个参数是查找的起始下标
- 返回值:布尔值
- 注意:第二个参数:接受负值。正值超过数组长度,数组不会被搜索,返回 false。 负值绝对值超过长数组度,重置从 0 开始搜索。 因为 indexOf 方法不能识别 NaN;indexOf 方法检查是否包含某个值不够语义化,需要判断是否不等于-1,表达不够直观
eg1: const arr = [4, 5, 6, 7, 1, 7];
const flag = 4;
const arrIncludes = arr.includes(flag, -6); //true
2
3
# 38. JS 有哪些内置对象
Object 是 JavaScript 中所有对象的父对象
数据封装对象:Object、Array、Boolean、Number 和 String 其他对象:Function、Arguments、Math、Date、RegExp、Error
# 39. get 传参长度的误区
实际上 HTTP 协议从未规定 GET/POST 的请求长度限制是多少。对 get 请求参数的限制是来源与浏览器或 web 服务器,浏览器或 web 服务器限制了 url 的长度。为了明确这个概念,我们必须再次强调下面几点:
- HTTP 协议 未规定 GET 和 POST 的长度限制
- GET 的最大长度显示是因为 浏览器和 web 服务器限制了 URI 的长度
- 不同的浏览器和 WEB 服务器,限制的最大长度不一样
- 要支持 IE,则最大长度为 2083byte,若只支持 Chrome,则最大长度 8182byte
# 40. get 和 post 请求在缓存方面的区别
- get 请求类似于查找的过程,用户获取数据,可以不用每次都与数据库连接,所以可以使用缓存。
- post 不同,post 做的一般是修改和删除的工作,所以必须与数据库交互,所以不能使用缓存。因此 get 请求适合于请求缓存。
# 41. 作用域和闭包
# 1.执行上下文
执行上下文就是当前 JavaScript 代码被解析和执行时所在环境的抽象概念, JavaScript 中运行任何的代码都是在执行上下文中运行。 执行上下文的生命周期包括三个阶段:创建阶段 → 执行阶段 → 回收阶段,我们重点介绍创建阶段。
创建阶段(当函数被调用但未执行时):
- 创建变量对象:初始化函数参数 args, 提升函数声明和变量声明
- 创建作用域链
- 确定 this 指向
function test(arg) {
// 1. 形参 arg 是 "hi"
// 2. 因为函数声明比变量声明优先级高,所以此时 arg 是 function
console.log(arg);
var arg = "hello"; // 3.var arg 变量声明被忽略, arg = 'hello'被执行
function arg() {
console.log("hello world");
}
console.log(arg);
}
test("hi");
/* 输出:
function arg() {
console.log('hello world');
}
hello
*/
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- 如果有形参,先给形参赋值
- 进行私有作用域中的预解释,函数声明优先级比变量声明高,最后后者会被前者所覆盖,但是可以重新赋值
- 私有作用域中的代码从上到下执行
JavaScript 引擎创建了执行栈来管理执行上下文。可以把执行栈认为是一个存储函数调用的栈结构,遵循先进后出的原则。
- JavaScript 执行在单线程上,所有的代码都是排队执行。
- 一开始浏览器执行全局的代码时,首先创建全局的执行上下文,压入执行栈的顶部。
- 每当进入一个函数的执行就会创建函数的执行上下文,并且把它压入执行栈的顶部。当前函数执行完成后,当前函数的执行上下文出栈,并等待垃圾回收。
- 浏览器的 JS 执行引擎总是访问栈顶的执行上下文。
- 全局上下文只有唯一的一个,它在浏览器关闭时出栈。
# 2.作用域与作用域链
ES6 到来 JavaScript 有全局作用域、函数作用域和块级作用域(ES6 新增)。我们可以这样理解:作用域就是一个独立的地盘,让变量不会外泄、暴露出去。也就是说作用域最大的用处就是隔离变量,不同作用域下同名变量不会有冲突。 在介绍作用域链之前,先要了解下自由变量,如下代码中,console.log(a)要得到 a 变量,但是在当前的作用域中没有定义 a(可对比一下 b)。当前作用域没有定义的变量,这成为 自由变量。
自由变量的值如何得到 —— 向父级作用域(创建该函数的那个父级作用域)寻找。如果父级也没呢?再一层一层向上寻找,直到找到全局作用域还是没找到,就宣布放弃。这种一层一层的关系,就是作用域链 。
function F1() {
var a = 100;
return function() {
console.log(a);
};
}
function F2(f1) {
var a = 200;
console.log(f1());
}
var f1 = F1();
F2(f1); // 100
2
3
4
5
6
7
8
9
10
11
12
# 3.闭包
闭包经典例子
切记将 function 赋值给 arr[i]时并不执行函数,在使用 console.log(arra)时才会调用函数并通过作用域链寻找 i。在上例中,test()作用域中的 i 为 10,所以每次调用寻找 i 都为 10。在下例子中,let 隐式劫持了作用域(作用域链为 test > let 1 > arr1,保存了每个 i 并创建了 10 条作用域链,所以每次调用 arri时会找到对应的一条作用域链)
function test() {
var arr = [];
for (var i = 0; i < 10; i++) {
arr[i] = function() {
return i;
};
}
for (var a = 0; a < 10; a++) {
console.log(arr[a]());
}
}
test(); // 连续打印 10 个 10
function test() {
var arr = [];
for (let i = 0; i < 10; i++) {
// 仅在这里作出了改动
arr[i] = function() {
return i;
};
}
for (var a = 0; a < 10; a++) {
console.log(arr[a]());
}
}
test(); // 打印 0 到 9
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
《JavaScript 高级程序设计》这样描述:
闭包是指有权访问另一个函数作用域中的变量的函数;
《JavaScript 权威指南》这样描述:
从技术的角度讲,所有的 JavaScript 函数都是闭包:它们都是对象,它们都关联到作用域链。
《你不知道的 JavaScript》这样描述:
当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行。
闭包应用:
- 模块暴露
function module() {
var arr = [];
function add(val) {
if (typeof val == "number") {
arr.push(val);
}
}
function get(index) {
if (index < arr.length) {
return arr[index];
} else {
return null;
}
}
return {
add: add,
get: get
};
}
var mod1 = module();
mod1.add(1);
mod1.add(2);
mod1.add("xxx");
console.log(mod1.get(2));
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
模块模式需要具备两个必要条件:
- 必须有外部的封闭函数,该函数必须至少被调用一次(每次调用都会创建一个新的模块 实例)。
- 封闭函数必须返回至少一个内部函数,这样内部函数才能在私有作用域中形成闭包,并 且可以访问或者修改私有的状态。
# 42.组件化和模块化
# 组件化
为什么要组件化开发:
有时候页面代码量太大,逻辑太多或者同一个功能组件在许多页面均有使用,维护起来相当复杂,这个时候,就需要组件化开发来进行功能拆分、组件封装,已达到组件通用性,增强代码可读性,维护成本也能大大降低
组件化开发的优点:
很大程度上降低系统各个功能的耦合性,并且提高了功能内部的聚合性。这对前端工程化及降低代码的维护来说,是有很大的好处的,耦合性的降低,提高了系统的伸展性,降低了开发的复杂度,提升开发效率,降低开发成本
原则:
- 专一
- 可配置性
- 标准性
- 复用性
- 可维护性
# 模块化
为什么要模块化:
早期的 javascript 版本没有块级作用域、没有类、没有包、也没有模块,这样会带来一些问题,如复用、依赖、冲突、代码组织混乱等,随着前端的膨胀,模块化显得非常迫切
模块化的好处:
- 避免变量污染,命名冲突
- 提高代码复用率
- 提高了可维护性
- 方便依赖关系管理
模块化的几种方法:
- 函数封装
var myModule = {
var1: 1,
var2: 2,
fn1: function() {},
fn2: function() {}
};
//总结:这样避免了变量污染,只要保证模块名唯一即可,同时同一模块内的成员也有了关系
//缺陷:外部可以睡意修改内部成员,这样就会产生意外的安全问题
2
3
4
5
6
7
8
9
10
11
12
- 立即执行函数表达式(IIFE)
var myModule = (function() {
var var1 = 1;
var var2 = 2;
function fn1() {}
function fn2() {}
return {
fn1: fn1,
fn2: fn2
};
})();
2
3
4
5
6
7
8
9
10
11
12
13
//总结:这样在模块外部无法修改我们没有暴露出来的变量、函数
//缺点:功能相对较弱,封装过程增加了工作量,仍会导致命名空间污染可能、闭包是有成本的
# 43.图片的预加载和懒加载
- 预加载:提前加载图片,当用户需要查看时可直接从本地缓存中渲染
- 懒加载:懒加载的主要目的是作为服务器前端的优化,减少请求数或延迟请求数
两种技术的本质:两者的行为是相反的,一个是提前加载,一个是迟缓甚至不加载。预加载则会增加服务器前端压力,懒加载对服务器有一定的缓解压力作用。
# 44. mouseover / mouseenter
- mouseover:当鼠标移入元素或其子元素都会触发事件,所以有一个重复触发,冒泡的过程。对应的移除事件是 mouseout
- mouseenter:当鼠标移入元素本身(不包含元素的子元素)会触发事件,也就是不会冒泡,对应的移除事件是 mouseleave
# 45. this 解析
// 情况1
function foo() {
console.log(this.a) //1
}
var a = 1
foo()
// 情况2
function fn(){
console.log(this);
}
var obj={fn:fn};
obj.fn(); //this->obj
// 情况3
function CreateJsPerson(name,age){
//this是当前类的一个实例p1
this.name=name; //=>p1.name=name
this.age=age; //=>p1.age=age
}
var p1=new CreateJsPerson("尹华芝",48);
// 情况4
function add(c, d){
return this.a + this.b + c + d;
}
var o = {a:1, b:3};
add.call(o, 5, 7); // 1 + 3 + 5 + 7 = 16
add.apply(o, [10, 20]); // 1 + 3 + 10 + 20 = 34
// 情况5
<button id="btn1">箭头函数this</button>
<script type="text/javascript">
let btn1 = document.getElementById('btn1');
let obj = {
name: 'kobe',
age: 39,
getName: function () {
btn1.onclick = () => {
console.log(this);//obj
};
}
};
obj.getName();
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45