ALEX CODE PARK

vuePress-theme-reco ALEX CODE PARK    2020
ALEX CODE PARK

Choose mode

  • dark
  • auto
  • light
阅读笔记
  • 《深入浅出Vue.js》
  • 《JavaScript高级程序设计》
  • 《你不知道的JavaScript》
  • 《CSS世界》
前端面试
  • CSS
  • HTML
  • JavaScript
  • 常见算法
  • 网络相关
  • 剑指Offer
Github
Tag
Category
  • 前端面试
  • 算法题目
  • 前端书籍
  • 个人笔记
author-avatar

ALEX CODE PARK

19

文章

10

标签

阅读笔记
  • 《深入浅出Vue.js》
  • 《JavaScript高级程序设计》
  • 《你不知道的JavaScript》
  • 《CSS世界》
前端面试
  • CSS
  • HTML
  • JavaScript
  • 常见算法
  • 网络相关
  • 剑指Offer
Github
Tag
Category
  • 前端面试
  • 算法题目
  • 前端书籍
  • 个人笔记
  • 前端面试相关

    • CSS面试题
    • HTML面试题
    • JavaScript面试题
      • 1.写一个方法去掉字符串中的空格
      • 2.正则表达式详解
      • 3.写一个方法把下划线命名转成大驼峰命名
      • 4.写一个方法切换字符大小写
      • 5.写一个去除制表符和换行符的方法
      • 6.写一个判断数据类型的方法
      • 7.JS 所有内置对象属性和方法汇总
      • 8.自定义实现一个 assign 方法
      • 9.冒泡排序
      • 10.this 详解
      • 11.call,apply,bind
      • 12.详解 JS 函数柯里化
      • 14.复制对象
      • 15.对象 Descriptor 操作
      • 16.ES6 拓展运算符...
      • 17.Object 手写 Symbol.iterator
      • 18.显式多态
      • 19.手写实现 hash 路由
      • 20.设计模式举例
      • 21.理解模块机制 (ES6 之前)
      • 24. 通过 Generator 让原生对象可迭代
      • 25. next 方法的参数
      • 26. next()、throw()、return() 的共同点
      • 27. yield*
      • 28. Generator 实现状态机
      • 29. Generator 封装异步任务
      • 30. async
      • 31. 立即执行匿名函数
      • 32. 继承
      • 33. JS 几条基本规范
      • 34. JS 引用方法
      • 35. JS 的基本数据类型
      • 36. 数组遍历
      • 37. 数组普通操作
      • 38. JS 有哪些内置对象
      • 39. get 传参长度的误区
      • 40. get 和 post 请求在缓存方面的区别
      • 41. 作用域和闭包
      • 42.组件化和模块化
      • 43.图片的预加载和懒加载
      • 44. mouseover / mouseenter
      • 45. this 解析
    • JavaScript面试题 Q&A
    • JavaScript手写实现汇总
    • 常见算法题
    • 网络面试题
  • 剑指Offer

JavaScript面试题

vuePress-theme-reco ALEX CODE PARK    2020

JavaScript面试题


ALEX CODE PARK 2020-02-05 JavaScript ES6

必看: 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 所有内置对象属性和方法汇总

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.冒泡排序

js/Untitled.png

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 详解

js/Untitled1.png

默认绑定:

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 拓展运算符...

Js 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

ECMAScript 6 入门

# 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>
1
2
3
4
  • 内部引入
<script>
  window.onload = function() {
    alert("js 内部引入!");
  };
</script>
1
2
3
4
5
  • 外部引入
<body>
  <div></div>
  <script type="text/javascript" src="./js/index.js"></script>
</body>
1
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;
});
1
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
1
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]
1
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);只是改变了序列化而已,并不能还原数组
1
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
1
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"]
1
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 可以看出,使用了展开运算符更加简洁
1
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
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
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
1
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
    */
1
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
1
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
1
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));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

模块模式需要具备两个必要条件:

  1. 必须有外部的封闭函数,该函数必须至少被调用一次(每次调用都会创建一个新的模块 实例)。
  2. 封闭函数必须返回至少一个内部函数,这样内部函数才能在私有作用域中形成闭包,并 且可以访问或者修改私有的状态。

# 42.组件化和模块化

# 组件化

  • 为什么要组件化开发:

    有时候页面代码量太大,逻辑太多或者同一个功能组件在许多页面均有使用,维护起来相当复杂,这个时候,就需要组件化开发来进行功能拆分、组件封装,已达到组件通用性,增强代码可读性,维护成本也能大大降低

  • 组件化开发的优点:

    很大程度上降低系统各个功能的耦合性,并且提高了功能内部的聚合性。这对前端工程化及降低代码的维护来说,是有很大的好处的,耦合性的降低,提高了系统的伸展性,降低了开发的复杂度,提升开发效率,降低开发成本

  • 原则:

    • 专一
    • 可配置性
    • 标准性
    • 复用性
    • 可维护性

# 模块化

  • 为什么要模块化:

    早期的 javascript 版本没有块级作用域、没有类、没有包、也没有模块,这样会带来一些问题,如复用、依赖、冲突、代码组织混乱等,随着前端的膨胀,模块化显得非常迫切

  • 模块化的好处:

    • 避免变量污染,命名冲突
    • 提高代码复用率
    • 提高了可维护性
    • 方便依赖关系管理
  • 模块化的几种方法:

    • 函数封装
var myModule = {
  var1: 1,

  var2: 2,

  fn1: function() {},

  fn2: function() {}
};

//总结:这样避免了变量污染,只要保证模块名唯一即可,同时同一模块内的成员也有了关系
//缺陷:外部可以睡意修改内部成员,这样就会产生意外的安全问题
1
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
  };
})();
1
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>
1
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

js/Untitled2.png