注册
环信即时通讯云

环信即时通讯云

单聊、群聊、聊天室...
环信开发文档

环信开发文档

Demo体验

Demo体验

场景Demo,开箱即用
RTE开发者社区

RTE开发者社区

汇聚音视频领域技术干货,分享行业资讯
技术讨论区

技术讨论区

技术交流、答疑
资源下载

资源下载

收集了海量宝藏开发资源
iOS Library

iOS Library

不需要辛辛苦苦的去找轮子, 这里都有
Android Library

Android Library

不需要辛辛苦苦的去找轮子, 这里都有

Node交互式命令行工具开发——自动化文档工具

 nodejs开发命令行工具,流程相对简单,但一套完整的命令行程序开发流程下来,还是需要下点功夫,网上资料大多零散,这篇教程意在整合一下完整的开发流程。  npm上命令行开发相关包很多,例如minimist、optimist、nopt、commander.js...
继续阅读 »

 nodejs开发命令行工具,流程相对简单,但一套完整的命令行程序开发流程下来,还是需要下点功夫,网上资料大多零散,这篇教程意在整合一下完整的开发流程。
  npm上命令行开发相关包很多,例如minimistoptimistnoptcommander.jsyargs等等,使用方法和效果类似。其中用得比较多的是TJ大神的commanderyargs,本文以commander为基础讲述,可以参考这篇教程,yargs教程可以参考阮大神的或者这一篇
  另外,一个完整的命令行工具开发,还需要了解processshelljspathlinebyline等模块,这些都是node基础模块或一些简单模块,非常简单,就不多说了,另外如果你不想用回调函数处理异步还需要了解一下PromiseGenerator函数。这是教程:i5ting大神的《深入浅出js(Node.js)异步流程控制》和阮大神的异步编程教程以及promise小人书,另外想尝试ES7 stage3阶段的async/await异步解决方案,可参考这篇教程async/await解决方案需要babel转码,这是教程。本人喜欢async/await(哪个node开发者不喜欢呢?)但不喜欢倒腾,况且async/await本身就是Promise的语法糖,所以没选择使用,据江湖消息,nodejs将在今年晚些时候(10月份?)支持async/await,很是期待。
  以下是文章末尾实例用到的一些依赖。

"dependencies": {
"bluebird": "^3.4.1",
"co": "^4.6.0",
"colors": "^1.1.2",
"commander": "^2.9.0",
"dox": "^0.9.0",
"handlebars": "^4.0.5",
"linebyline": "^1.3.0",
"mkdirp": "^0.5.1"
}

 其中bluebird用于Promise化,TJ大神的co用于执行Generator函数,handlebars是一种模板,linebyline用于分行读取文件,colors用于美化输出,mkdirp用于创建目录,另外教程中的示例是一款工具,可以自动化生成数据库和API接口的markdown文档,并通过修改git hooks,使项目的每次commit都会自动更新文档,借助了TJ大神的dox模块。
  <span style="color:rgb(0, 136, 204)">所有推荐教程/教材,仅供参考,自行甄选阅读。</span>

安装Node

  各操作系统下安装见Nodejs官网,安装完成之后用node -v或者which node等命令测试安装是否成功。which在命令行开发中是一个非常有用的命令,使用which命令确保你的系统中不存在名字相同的命令行工具,例如which commandName,例如which testdev命令返回空白那么说明testdev命令名称还没有被使用。

初始化

  1. 新建一个.js文件,即是你的命令要执行的主程序入口文件,例如testdev.js。在文件第一行加入#!/usr/bin/env node指明系统在运行这个文件的时候使用node作为解释器,等价于node testdev.js命令。
  2. 初始化package.json文件,使用npm init命令根据提示信息创建,也可以是使用npm init -y使用默认设置创建。创建完成之后需要修改package.json文件内容加入"bin": {"testdev": "./testdev.js"}这条信息用于告诉npm你的命令(testdev)要执行的脚本文件的路径和名字,这里我们指定testdev命令的执行文件为当前目录下的testdev.js文件。
  3. 为了方便测试在testdev.js文件中加入代码console.log('hello world');,这里只是用于测试环境是否搭建成功,更加复杂的程序逻辑和过程需要按照实际情况进行编写

测试

  使用npm link命令,可以在本地安装刚刚创建的包,然后就可以用testdev来运行命令了,如果正常的话在控制台会打印出hello world

commander

  TJ的commander非常简洁,README.md已经把使用方法写的非常清晰。下面是例子中的代码:

const program = require('commander'),
co = require('co');

const appInfo = require('./../package.json'),
asyncFunc = require('./../common/asyncfunc.js');

program.allowUnknownOption();
program.version(appInfo.version);

program
.command('init')
.description('初始化当前目录doc.json文件')
.action(() => co(asyncFunc.initAction));

program
.command('show')
.description('显示配置文件状态')
.action(() => co(asyncFunc.showAction));

program
.command('run')
.description('启动程序')
.action(() => co(asyncFunc.runAction));

program
.command('modifyhook')
.description('修改项目下的hook文件')
.action(() => co(asyncFunc.modifyhookAction));

program
.command('*')
.action((env) => {
console.error('不存在命令 "%s"', env);
});

program.on('--help', () => {
console.log(' Examples:');
console.log('');
console.log(' $ createDOC --help');
console.log(' $ createDOC -h');
console.log(' $ createDOC show');
console.log('');
});

program.parse(process.argv);

 定义了四个命令和个性化帮助说明。

交互式命令行process

  commander只是实现了命令行参数与回复一对一的固定功能,也就是一个命令必然对应一个回复,那如何实现人机交互式的命令行呢,类似npm init或者eslint --init这样的与用户交互,交互之后根据用户的不同需求反馈不同的结果呢。这里就需要node内置的process模块。
  这是我实现的一个init命令功能代码:

exports.initAction = function* () {
try {
var docPath = yield exists(process.cwd() + '/doc.json');
if (docPath) {
func.initRepl(config.coverInit, arr => {
co(newDoc(arr));
})
} else {
func.initRepl(config.newInit, arr => {
co(newDoc(arr));
})
}
} catch (err) {
console.warn(err);
}

首先检查doc.json文件是否存在,如果存在执行覆盖交互,如果不存在执行生成交互,try...catch捕获错误。
  交互内容配置如下:

newInit:
[
{
title:'initConfirm',
description:'初始化createDOC,生成doc.json.确认?(y/n) ',
defaults: 'y'
},
{
title:'defaultConfirm',
description:'是否使用默认配置.(y/n) ',
defaults: 'y'
},
{
title:'showConfig',
description:'是否显示doc.json当前配置?(y/n) ',
defaults: 'y'
}
],
coverInit:[
{
title:'modifyConfirm',
description:'doc.json已存在,初始化将覆盖文件.确认?(y/n) ',
defaults: 'y'
},
{
title:'defaultConfirm',
description:'是否使用默认配置.(y/n) ',
defaults: 'y'
},
{
title:'showConfig',
description:'是否显示doc.json当前配置?(y/n) ',
defaults: 'y'
}
],

人机交互部分代码也就是initRepl函数内容如下:

//初始化命令,人机交互控制
exports.initRepl = function (init, func) {
var i = 1;
var inputArr = [];
var len = init.length;
process.stdout.write(init[0].description);
process.stdin.resume();
process.stdin.setEncoding('utf-8');
process.stdin.on('data', (chunk) => {
chunk = chunk.replace(/[\s\n]/, '');
if (chunk !== 'y' && chunk !== 'Y' && chunk !== 'n' && chunk !== 'N') {
console.log(config.colors.red('您输入的命令是: ' + chunk));
console.warn(config.colors.red('请输入正确指令:y/n'));
process.exit();
}
if (
(init[i - 1].title === 'modifyConfirm' || init[i - 1].title === 'initConfirm') &&
(chunk === 'n' || chunk === 'N')
) {
process.exit();
}
var inputJson = {
title: init[i - 1].title,
value: chunk,
};
inputArr.push(inputJson);
if ((len--) > 1) {
process.stdout.write(init[i++].description)
} else {
process.stdin.pause();
func(inputArr);
}
});
}

人机交互才用向用户提问根据用户不同输入产生不同结果的形式进行,顺序读取提问列表并记录用户输入结果,如果用户输入n/N则终止交互,用户输入非法字符(除y/Y/n/N以外)提示输入命令错误。

文档自动化

  文档自动化,其中数据库文档自动化,才用依赖sequelize的方法手写(根据需求不同自行编写逻辑),API文档才用TJ的dox也很简单。由于此处代码与命令行功能相关度不大,请读者自行去示例地址查看代码。

示例地址

github地址
npm地址

原文链接:https://segmentfault.com/a/1190000039749423

收起阅读 »

JS前端面试总结

ES5的继承和ES6的继承有什么区别ES5的继承时通过prototype或构造函数机制来实现。ES5的继承实质上是先创建子类的实例对象,然后再将父类的方法添加到this上(Parent.apply(this))。ES6的继承机制完全不同,实质上是先创建父类的实...
继续阅读 »

ES5的继承和ES6的继承有什么区别

ES5的继承时通过prototype或构造函数机制来实现。ES5的继承实质上是先创建子类的实例对象,然后再将父类的方法添加到this上(Parent.apply(this))。
ES6的继承机制完全不同,实质上是先创建父类的实例对象this(所以必须先调用父类的super()方法),然后再用子类的构造函数修改this。
具体的:ES6通过class关键字定义类,里面有构造方法,类之间通过extends关键字实现继承。子类必须在constructor方法中调用super方法,否则新建实例报错。因为子类没有自己的this对象,而是继承了父类的this对象,然后对其进行加工。如果不调用super方法,子类得不到this对象。
ps:super关键字指代父类的实例,即父类的this对象。在子类构造函数中,调用super后,才可使用this关键字,否则报错。

如何实现一个闭包?闭包的作用有哪些

在一个函数里面嵌套另一个函数,被嵌套的那个函数的作用域是一个闭包。
作用:创建私有变量,减少全局变量,防止变量名污染。可以操作外部作用域的变量,变量不会被浏览器回收,保存变量的值。

介绍一下 JS 有哪些内置对象

Object 是 JavaScript 中所有对象的父对象
数据封装类对象:Object、Array、Boolean、Number、String
其他对象:Function、Argument、Math、Date、RegExp、Error

new 操作符具体干了什么呢

(1)创建一个空对象,并且 this 变量引用该对象,同时还继承了该函数的原型。
(2)属性和方法被加入到 this 引用的对象中。
(3)新创建的对象由 this 所引用,并且最后隐式的返回 this 。

同步和异步的区别

同步的概念应该是来自于操作系统中关于同步的概念:不同进程为协同完成某项工作而在先后次序上调整(通过阻塞,唤醒等方式)。
同步强调的是顺序性,谁先谁后;异步则不存在这种顺序性。

同步:浏览器访问服务器请求,用户看得到页面刷新,重新发请求,等请求完,页面刷新,新内容出现,用户看到新内容,进行下一步操作。

异步:浏览器访问服务器请求,用户正常操作,浏览器后端进行请求。等请求完,页面不刷新,新内容也会出现,用户看到新内容。

异步解决方式优缺点

回调函数(callback)

缺点:回调地狱,不能用 try catch 捕获错误,不能 return
优点:解决了同步的问题

Promise

Promise就是为了解决callback的问题而产生的。
回调地狱的根本问题在于:

缺乏顺序性: 回调地狱导致的调试困难,和大脑的思维方式不符
嵌套函数存在耦合性,一旦有所改动,就会牵一发而动全身,即(控制反转)
嵌套函数过多的多话,很难处理错误

Promise 实现了链式调用,也就是说每次 then 后返回的都是一个全新 Promise,如果我们在 then 中 return ,return 的结果会被 Promise.resolve() 包装

优点:解决了回调地狱的问题
缺点:无法取消 Promise ,错误需要通过回调函数来捕获

Generator

特点:可以控制函数的执行,可以配合 co 函数库使用

Async/await

async、await 是异步的终极解决方案

优点:代码清晰,不用像 Promise 写一大堆 then 链,处理了回调地狱的问题
缺点:await 将异步代码改造成同步代码,如果多个异步操作没有依赖性而使用 await 会导致性能上的降低。

null 和 undefined 的区别

null: null表示空值,转为数值时为0;
undefined:undefined表示"缺少值",就是此处应该有一个值,但是还没有定义。

• 变量被声明了,但没有赋值时,就等于undefined。
• 对象没有赋值的属性,该属性的值为undefined。
• 函数没有返回值时,默认返回undefined。

JavaScript 原型,原型链 ? 有什么特点?

JavaScript 原型: 每创建一个函数,函数上都有一个属性为 prototype,它的值是一个对象。 这个对象的作用在于当使用函数创建实例的时候,那么这些实例都会共享原型上的属性和方法。

原型链: 在 JavaScript 中,每个对象都有一个指向它的原型(prototype)对象的内部链接(proto)。这个原型对象又有自己的原型,直到某个对象的原型为 null 为止(也就是不再有原型指向)。这种一级一级的链结构就称为原型链(prototype chain)。 当查找一个对象的属性时,JavaScript 会向上遍历原型链,直到找到给定名称的属性为止;到查找到达原型链的顶部(Object.prototype),仍然没有找到指定的属性,就会返回 undefined

如何获取一个大于等于0且小于等于9的随机整数

function randomNum(){
return Math.floor(Math.random()*10)
}

想要去除一个字符串的第一个字符,有哪些方法可以实现str.slice(1)

 str.substr(1)
str.substring(1)
str.replace(/./,'')
str.replace(str.charAt(0),'')

JavaScript的组成

JavaScript 由以下三部分组成:

ECMAScript(核心):JavaScript 语言基础
DOM(文档对象模型):规定了访问HTML和XML的接口
BOM(浏览器对象模型):提供了浏览器窗口之间进行交互的对象和方法

到底什么是前端工程化、模块化、组件化

前端工程化就是用做工程的思维看待和开发自己的项目,
而模块化和组件化是为工程化思想下相对较具体的开发方式,因此可以简单的认为模块化和组件化是工程化的表现形式。
模块化和组件化一个最直接的好处就是复用,同时我们也应该有一个理念,模块化和组件化除了复用之外还有就是分治,我们能够在不影响其他代码的情况下按需修改某一独立的模块或是组件,因此很多地方我们及时没有很强烈的复用需要也可以根据分治需求进行模块化或组件化开发。
模块化开发的4点好处:

  1 避免变量污染,命名冲突
  2 提高代码复用率
  3 提高维护性
4 依赖关系的管理

前端模块化实现的过程如下:
一 函数封装
我们在讲到函数逻辑的时候提到过,函数一个功能就是实现特定逻辑的一组语句打包,在一个文件里面编写几个相关函数就是最开始的模块了

function m1(){
    //...
  }

  function m2(){
    //...
  }

这样做的缺点很明显,污染了全局变量,并且不能保证和其他模块起冲突,模块成员看起来似乎没啥关系
二 对象
为了解决这个问题,有了新方法,将所有模块成员封装在一个对象中

var module = new Object({

_count:0,

m1:function (){ ``` },

m2:function (){ ``` }

})

这样 两个函数就被包在这个对象中, 嘿嘿 看起来没毛病是吗 继续往下:
当我们要使用的时候,就是调用这个对象的属性
module.m1()
诶嘿 那么问题来了 这样写法会暴露全部的成员,内部状态可以被外部改变,比如外部代码可直接改变计数器的值
//坏人的操作

module._count = 10;

最后的最后,聪明的人类找到了究极新的方法——立即执行函数,这样就可以达到不暴露私有成员的目的

var module = (function (){

var _count = 5;

var m1 = function (){ ``` };

var m2 = function (){ ``` };

return{
m1:m1,
m2:m2
}

})()

面向对象与面向过程

  1. 什么是面向过程与面向对象?

• 面向过程就是做围墙的时候,由你本身操作,叠第一层的时候:放砖头,糊水泥,放砖头,糊水泥;然后第二层的时候,继续放砖头,糊水泥,放砖头,糊水泥……
• 面向对象就是做围墙的时候,由他人帮你完成,将做第一层的做法抽取出来,就是放砖头是第一个动作,糊水泥是第二个动作,然后给这两个动作加上步数,最后告诉机器人有 n 层,交给机器人帮你工作就行了。

  1. 为什么需要面向对象写法?

• 更方便
• 可以复用,减少代码冗余度
• 高内聚低耦合
简单来说,就是增加代码的可复用性,减少咱们的工作,使代码更加流畅。

事件绑定和普通事件有什么区别

普通添加事件的方法:

var btn = document.getElementById("hello");
btn.onclick = function(){
alert(1);
}
btn.onclick = function(){
alert(2);
}

执行上面的代码只会alert 2

事件绑定方式添加事件:

var btn = document.getElementById("hello");
btn.addEventListener("click",function(){
alert(1);
},false);
btn.addEventListener("click",function(){
alert(2);
},false);

垃圾回收

由于字符串、对象和数组没有固定大小,所有当他们的大小已知时,才能对他们进行动态的存储分配。JavaScript程序每次创建字符串、数组或对象时,解释器都必须分配内存来存储那个实体。只要像这样动态地分配了内存,最终都要释放这些内存以便他们能够被再用,否则,JavaScript的解释器将会消耗完系统中所有可用的内存,造成系统崩溃。
  现在各大浏览器通常用采用的垃圾回收有两种方法:标记清除、引用计数。
1、标记清除
  这是javascript中最常用的垃圾回收方式。当变量进入执行环境是,就标记这个变量为“进入环境”。从逻辑上讲,永远不能释放进入环境的变量所占用的内存,因为只要执行流进入相应的环境,就可能会用到他们。当变量离开环境时,则将其标记为“离开环境”。
  垃圾收集器在运行的时候会给存储在内存中的所有变量都加上标记。然后,它会去掉环境中的变量以及被环境中的变量引用的标记。而在此之后再被加上标记的变量将被视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量了。最后。垃圾收集器完成内存清除工作,销毁那些带标记的值,并回收他们所占用的内存空间。
关于这一块,建议读读Tom大叔的几篇文章,关于作用域链的一些知识详解,读完差不多就知道了,哪些变量会被做标记。

2、引用计数
  另一种不太常见的垃圾回收策略是引用计数。引用计数的含义是跟踪记录每个值被引用的次数。当声明了一个变量并将一个引用类型赋值给该变量时,则这个值的引用次数就是1。相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用次数就减1。当这个引用次数变成0时,则说明没有办法再访问这个值了,因而就可以将其所占的内存空间给收回来。这样,垃圾收集器下次再运行时,它就会释放那些引用次数为0的值所占的内存。


原文链接:https://segmentfault.com/a/1190000018077712


收起阅读 »

面向面试编程,面向掘金面试

我使用 curl 与 jq 一行简单的命令爬取了掘金的面试集合榜单,有兴趣的同学可以看看爬取过程: 使用 jq 与 sed 制作掘金面试文章排行榜,可以提高你使用命令行的乐趣关于前端,后端,移动端的面试,这里统...
继续阅读 »

我使用 curl 与 jq 一行简单的命令爬取了掘金的面试集合榜单,有兴趣的同学可以看看爬取过程: 使用 jq 与 sed 制作掘金面试文章排行榜,可以提高你使用命令行的乐趣

关于前端,后端,移动端的面试,这里统统都有,希望可以在面试的过程中帮助到你。另外我也有一个仓库 日问 来记录前后端以及 devops 一些有意思的问题,欢迎交流

前端

后端

Android/IOS

原文:https://segmentfault.com/a/1190000021037487

收起阅读 »

vue 自动化路由实现

1、需求描述在写vue的项目中,一般情况下我们每添加一个新页面都得添加一个新路由。为此我们在项目中会专门的一个文件夹来管理路由,如下图所示那么有没有一种方案,能够实现我们在文件夹中新建了一个vue文件,就自动帮我们添加路由。特别在我们的一个ERP后台项目中,我...
继续阅读 »

1、需求描述

在写vue的项目中,一般情况下我们每添加一个新页面都得添加一个新路由。为此我们在项目中会专门的一个文件夹来管理路由,如下图所示


那么有没有一种方案,能够实现我们在文件夹中新建了一个vue文件,就自动帮我们添加路由。特别在我们的一个ERP后台项目中,我们几乎都是一个文件夹下有很多子文件,子文件中一般包含index.vue, detail.vue, edit.vue分别对应的事列表页,详情页和编辑页。


 上图是我们的文件目录,views文件夹中存放的是所有的页面,goodsPlanning是一级目录,onNewComplete和thirdGoods是二级目录,二级目录中存放的是具体的页面,indexComponents中存放的是index.vue的文件,editComponents也是同样的道理。index.vue对应的路由是/goodsPlanning/onNewComplete, edit.vue对应的路由是/goodsPlanning/onNewComplete/edit,detail.vue也是同样的道理。所以我们的文件夹和路由是完全能够对应上的,只要知道路由,就能很快的找到对应的文件。那么有没有办法能够读取我们二级目录下的所有文件,然后根据文件名来生成路由呢?答案是有的


2 、require.context介绍

简单说就是:有了require.context,我们可以得到指定文件夹下的所有文件

require.context(directory, useSubdirectories = false, regExp = /^\.\//);

require.context有三个参数:

  • directory:说明需要检索的目录
  • useSubdirectories:是否检索子目录
  • regExp: 匹配文件的正则表达式

require.context()的返回值,有一个keys方法,返回的是个数组

let routers = require.context('VIEWS', true).keys()
console.log(routers)



 通过上面的代码,我们打印出了所有的views文件夹下的所有文件和文件夹,我们只要写好正则就能找到我们所需要的文件


3、 直接上代码

import Layout from 'VIEWS/layout/index'

/**
* 正则 首先匹配./ ,然后一级目录,不包含components的二级目录,以.vue结尾的三级目录
*/
let routers = require.context('VIEWS', true, /\.\/[a-z]+\/(?!components)[a-z]+\/[a-z]+\.vue$/i).keys()
let indexRouterMap = {} // 用来存储以index.vue结尾的文件,因为index.vue是列表文件,需要加入layout(我们的菜单),需要keepAlive,需要做权限判断
let detailRouterArr = [] // 用来存储以非index.vue结尾的vue文件,此类目前不需要layout
routers.forEach(item => {
const paths = item.match(/[a-zA-Z]+/g) //paths中存储了一个目录,二级目录,文件名
const routerChild = { //定义路由对象
path: paths[1],
name: `${paths[0]}${_.upperFirst(paths[1])}`, //upperFirst,lodash 首字母大写方法
component(resolve) {
require([`../../views${item.slice(1)}`], resolve)
},
}
if (/index\.vue$/.test(item)) { //判断是否以indexvue结尾
if (indexRouterMap[paths[0]]) { //判断一级路由是否存在,存在push二级路由,不存在则新建
indexRouterMap[paths[0]].children.push(routerChild)
} else {
indexRouterMap[paths[0]] = {
path: '/' + paths[0],
component: Layout,
children: [routerChild]
}
}
} else { //不以index.vue结尾的,直接添加到路由中
detailRouterArr.push({
path: item.slice(1, -4), //渠道最前面的 . 和最后的.vue
name: `${paths[0]}${_.upperFirst(paths[1])}${_.upperFirst(paths[2])}`,
component(resolve) {
require([`../../views${item.slice(1)}`], resolve)
},
meta: {
noCache: true, //不keepAlive
noVerify: true //不做权限验证
}
})
}
})

export default [
...Object.values(indexRouterMap),
...detailRouterArr,
/**
* dashboard单独处理下
*/
{
path: '',
component: Layout,
redirect: 'dashboard',
children: [
{
path: 'dashboard',
component: () => import('VIEWS/dashboard/index'),
name: 'dashboard',
meta: { title: '首页', noCache: true, noVerify: true }
}
]
},
]

简简单单的几十行代码就实现了所有的路由功能,再也不用一行一行的写路由文件了。可能你的文件管理方式和我的不一样,但是只要稍微改改正则就行了。


4、 注意

  1. 不能用import引入路由因为用import引入不支持变量
  2. 不能用别名找了半天问题,才知道用变量时也不能用别名,所以我用的都是相对路径


5、 好处

  • 不用在添加路由了,这个就不说了,明眼人都看得出来
  • 知道了路由,一个能找到对应的文件,以前我们团队就出现过,乱写path的情况
  • 更好的控制验证和keepAlive

原文链接:https://www.cnblogs.com/mianbaodaxia/p/11452123.html

收起阅读 »

前端自测清单(前端八股文)

缘起这篇文章主要列举一些自己想到的面试题目,让大家更加熟悉前端八股文。先从性能优化开始吧。性能优化大体可以分为两个,运行时优化加载时优化加载时优化网络优化dns寻址过程tcp的三次握手和四次挥手,以及为何要三次和为何要四次https的握手过程,以及对称加密和非...
继续阅读 »

/zi-ce-qing-dan/featured-image.jpg

缘起

这篇文章主要列举一些自己想到的面试题目,让大家更加熟悉前端八股文。

先从性能优化开始吧。性能优化大体可以分为两个,

  • 运行时优化
  • 加载时优化

加载时优化

网络优化

  • dns寻址过程
  • tcp的三次握手和四次挥手,以及为何要三次和为何要四次
  • https的握手过程,以及对称加密和非对称加密的区别,什么是中间人劫持,ca证书包括哪些内容
  • http1.0,http1.1以及http2.0的区别,多路复用具体指的是什么,keep-alive具体如何体现
  • cdn的原理,cdn什么情况下会回源,cdn的适用场景
  • 浏览器缓存有哪几种,它们的区别是什么,什么时候发生缓存,如何决定缓存哪些文件
  • 了解过websocket么,解释一下websocket的作用

渲染优化

  • 关键渲染路径优化,什么是关键渲染路径,分别如何优化
  • 优化体积,webpack的分包策略,如何配置优化,如何提高构建速度,tree-shaking是什么
  • cssom 的优化,以及html解析过程中,遇到哪些tag会阻塞渲染
  • 雅虎军规说,css尽量放到head里,js放到下方,那么移动端适配的flexiblejs为何要放到css上方呢
  • 影响回流重绘的因素有哪些,如何避免回流,以及bfc是什么,bfc有什么特性,清除浮动的原理是什么

场景:如何优化首屏

除了上以及下面说到的,这里也是分两个层面,

  • 加载时优化
  • 运行时优化

加载

  • 首屏请求和非首屏请求拆分
  • 图片都应该使用懒加载的形式加载
  • 使用preload预加载技术,以及prefetch的dns预解析
  • 与首屏无关的代码可以加async甚至是defer等待网页加载完成后运行

运行

这里跟加载的异常耦合,另作分析吧。

运行时优化

  • 虚拟长列表渲染
  • 图片懒加载
  • 使用事件委托
  • react memo以及pureComponent
  • 使用SSR
  • 。。。

以及一些比较骚的操作,只能特定场景使用,

  • serviceWorker劫持页面
  • 利用worker

更新一波,性能优化之外的面试题,

底层

  • V8是如何实现GC的
  • JS的let,const,call stack,function context,global context。。。的区别
  • this的指向,箭头函数中this和function里的this有什么区别
  • 原型链是什么,继承呢,有几种继承方式,如何实现es6的class
  • eventloop是什么,浏览器的eventloop和nodejs的eventloop有什么区别,nexttick是什么
  • commonjs和AMD,CMD的区别,以及跟ES MODULE的区别
  • 说说require.cache
  • 了解过,洋葱模型没有,它是如何实现的
  • 说说nodejs中的流,也就是stream
  • 你用过ts,说说你常用的ts中的数据类型
  • js的数据类型,weakMap,weakSet和Map以及Set的区别是什么
  • 为何0.1+0.2 不等于0.3,如何解决这个问题
  • js的类型转换
  • 正则表达式
  • 对象循环引用会发生什么问题
  • 如何捕获异步的异常,你能说出几种方案

CSS相关

  • position有哪几种属性,它们的区别是什么
  • 如何实现垂直居中,移动端的呢
  • margin设置百分比,是依据谁的百分比,padding呢
  • 怪异盒模型和一般盒模型有什么区别
  • flex:1代表什么,flex-shrink和flex-grow有什么区别
  • background-size origin基准点在哪里
  • 移动端1px解决方案,以及为何会产生这个问题
  • 移动端高清屏图片的解决方案
  • 说说GPU加速

跨端

  • RN 实现原理
  • 小程序实现原理
  • webview跟h5有什么区别
  • RPC 是什么
  • JSBridge 原理是什么
  • 网页唤起app的原理是什么

服务端

  • oauth2了解过没有,sso呢
  • JWT 如何实现的

网络

除了之前提到的网络问题,当然还有很多,比如

  • 为何使用1x1的gif进行请求埋点
  • TCP 如何进行拥塞控制

安全

  • csrf是什么,防范措施是什么
  • xss如何防范

浏览器相关

  • 跨域是如何产生的,如何解决
  • 如何检查性能瓶颈
  • 打开页面白屏,如何定位问题,或者打开页面CPU100%,如何定位问题
  • jsonp是什么,为何能解决跨域,使用它可能会产生什么问题
  • base64会产生什么问题
  • event.target和event.currTarget有什么区别

框架相关

  • react和vue的区别
  • react的调度原理
  • setstate为何异步
  • key的作用是什么,为何说要使用唯一key,react的diff算法是如何实现的,vue的呢
  • react的事件系统是如何实现的
  • react hook是如何实现的
  • react的通信方式,hoc的使用场景
  • 听过闭包陷阱么,为何会出现这种现象,如何避免
  • vue的响应式原理
  • 为何vue3.x用的是proxy而不是object.defineProperties
  • vue是如何实现对数据的监听的,对数组呢
  • vue中的nexttick是如何实现的
  • fiber是什么,简单说说时间切片如何实现,为何vue不需要时间切片
  • webpack是如何实现的,HMR是如何实现的,可以写个简单的webpack么,webpack的执行流程是怎样的
  • koa源码实现,洋葱模型原理,get/post等这些方法如何set入koa里的,ctx.body为何能直接改变response的body
  • 你简历上写的了解过webpack源码,到哪种程度了(实话说没写koa简单。。

算法相关

  • js大整数加法
  • 双指针
  • 经典排序
  • 动态规划
  • 贪心算法
  • 回溯法
  • DFS
  • BFS
  • 链表操作
  • 线性求值
  • 预处理,前缀和

项目相关

  • 项目中遇到的最大问题是什么,如何解决的
  • nodejs作为中间层的作用是什么

场景题(机试)

  • 如何实现直播上的弹幕组件,要求不能重叠,仿照b站上的弹幕
  • 如何实现动态表单,仿照antd上的form组件
  • 实现一个promise(一般不会这样问)
  • 实现一个限制请求数量的方法
  • 如何实现一个大文件的上传
  • 实现一个eventEmitter
  • 实现一个new,call,bind,apply
  • 实现一个throttle,debound
  • 实现promise.then,finally,all
  • 实现继承,寄生组合继承,instanceof
  • 实现Generator,Aynsc

20.11.20 更新来了

  • React 生命周期,(分三个阶段进行回答,挂载阶段,更新阶段以及卸载阶段)
  • Vue 生命周期 以及其父子组件的生命周期调度顺序
  • 如果让你用强缓存或者协商缓存来缓存资源的话,你会如何使用
  • 作用域是什么,作用域链呢?(这题我想了下,不会利用语言去表达这个东西。。)

目前暂时想到这些,后来有想到的会补充上去。

原文链接:https://steinw.cc/zi-ce-qing-dan/


收起阅读 »

前端如何进行用户权限管理

【前端如何进行用户权限管理】1:问题:假如在做一个管理系统,面向老师学生的,学生提交申请,老师负责审核(或者还需要添加其他角色,功能权限都不同)。现在的问题是,每种角色登录看到的界面应该都是不一样的,那这个页面的区分如何实现呢?2:要不要给老师和学生各自设计一...
继续阅读 »

【前端如何进行用户权限管理】

1:问题:
假如在做一个管理系统,面向老师学生的,学生提交申请,老师负责审核(或者还需要添加其他角色,功能权限都不同)。


现在的问题是,每种角色登录看到的界面应该都是不一样的,那这个页面的区分如何实现呢?

2:要不要给老师和学生各自设计一套页面?这样工作量是不是太大了,并且如果还要加入其它角色的话,难道每个角色对应一套代码?

所以我们需要用一套页面适应各种用户角色,并根据身份赋予他们不同权限

3:权限设计与管理是一个很复杂的问题,涉及的东西很多,相比前端,更偏向于后端,在搜集相关资料的过程中,发现掺杂了许多数据库之类的知识,以及几个用于权限管理的java框架,比如spring,比如shiro等等,都属于后端的工作

4:那我们前端能做什么呢?

权限的设计中比较常见的就是RBAC基于角色的访问控制,基本思想是,对系统操作的各种权限不是直接授予具体的用户,而是在用户集合与权限集合之间建立一个角色集合。每一种角色对应一组相应的权限。

一旦用户被分配了适当的角色后,该用户就拥有此角色的所有操作权限。这样做的好处是,不必在每次创建用户时都进行分配权限的操作,只要分配用户相应的角色即可,而且角色的权限变更比用户的权限变更要少得多,这样将简化用户的权限管理,减少系统的开销。

在Angular构建的单页面应用中,要实现这样的架构我们需要额外多做一些事.从整体项目上来讲,大约有3处地方,前端工程师需要进行处理.

1. UI处理(根据用户拥有的权限,判断页面上的一些内容是否显示)

2. 路由处理(当用户访问一个它没有权限访问的url时,跳转到一个错误提示的页面)

3. HTTP请求处理(当我们发送一个数据请求,如果返回的status是401或者401,则通常重定向到一个错误提示的页面)

如何实现?
首先需要在Angular启动之前就获取到当前用户的所有的permissions,然后比较优雅的方式是通过一个service存放这个映射关系.对于UI处理一个页面上的内容是否根据权限进行显示,我们应该通过一个directive来实现.当处理完这些,我们还需要在添加一个路由时额外为其添加一个"permission"属性,并为其赋值表明拥有哪些权限的角色可以跳转这个URL,然后通过Angular监听routeChangeStart事件来进行当前用户是否拥有此URL访问权限的校验.最后还需要一个HTTP拦截器监控当一个请求返回的status是401或者403时,跳转页面到一个错误提示页面.

大致上的工作就是这些,看起来有些多,其实一个个来还是挺好处理的.

在Angular运行之前获取到permission的映射关系



Angular项目通过ng-app启动,但是一些情况下我们是希望Angular项目的启动在我们的控制之中.比如现在这种情况下,我就希望能获取到当前登录用户的所有permission映射关系后,再启动Angular的App.幸运的是Angular本身提供了这种方式,也就是angular.bootstrap().看的仔细的人可能会注意到,这里使用的是$.get(),没有错用的是jQuery而不是Angular的$resource或者$http,因为在这个时候Angular还没有启动,它的function我们还无法使用.

进一步使用上面的代码可以将获取到的映射关系放入一个service作为全局变量来使用.


在取得当前用户的权限集合后,我们将这个集合存档到对应的一个service中,然后又做了2件事:

(1) 将permissions存放到factory变量中,使之一直处于内存中,实现全局变量的作用,但却没有污染命名空间.

(2) 通过$broadcast广播事件,当权限发生变更的时候.

如何确定UI组件的依据权限进行显隐




这里我们需要自己编写一个directive,它会依据权限关系来进行显示或者隐藏元素.

这里看到了比较理想的情况是通关一个has-permission属性校验permission的name,如果当前用户有则显示,没有则隐藏.




扩展一下之前的factory:




路由上的依权限访问
这一部分的实现的思路是这样: 当我们定义一个路由的时候增加一个permission的属性,属性的值就是有哪些权限才能访问当前url.然后通过routeChangeStart事件一直监听url变化.每次变化url的时候,去校验当前要跳转的url是否符合条件,然后决定是跳转成功还是跳转到错误的提示页面.

router.js:






mainController.js 或者 indexController.js (总之是父层Controller)





这里依然用到了之前写的hasPermission,这些东西都是高度可复用的.这样就搞定了,在每次view的route跳转前,在父容器的Controller中判断一些它到底有没有跳转的权限即可.



HTTP请求处理
这个应该相对来说好处理一点,思想的思路也很简单.因为Angular应用推荐的是RESTful风格的接口,所以对于HTTP协议的使用很清晰.对于请求返回的status code如果是401或者403则表示没有权限,就跳转到对应的错误提示页面即可.





当然我们不可能每个请求都去手动校验转发一次,所以肯定需要一个总的filter.代码如下:

写到这里我们就基本实现了在这种前后端分离模式下,前端部分的权限管理和控制。

原文链接:https://blog.csdn.net/jnshu_it/article/details/77511588


收起阅读 »

彻底解决小程序无法触发SESSION问题

一、首先找到第一次发起网络请求的地址,将服务器返回set-cookie当全局变量存储起来wx.request({ ...... success: function(res) { console.log(res.header); //set-co...
继续阅读 »

一、首先找到第一次发起网络请求的地址,将服务器返回set-cookie当全局变量存储起来

wx.request({
......
success: function(res) {
console.log(res.header);
//set-cookie:PHPSESSID=ic4vj84aaavqgb800k82etisu0; path=/; domain=.fengkui.net

// 登录成功,获取第一次的sessionid,存储起来
// 注意:Set-Cookie(开发者工具中调试全部小写)(远程调试和线上首字母大写)
wx.setStorageSync("sessionid", res.header["Set-Cookie"]);
}
})

二、请求时带上将sessionid放入request的header头中传到服务器,服务器端可直接在cookie中获取

wx.request({
......
header: {
'content-type': 'application/json', // 默认值
'cookie': wx.getStorageSync("sessionid")
//读取sessionid,当作cookie传入后台将PHPSESSID做session_id使用
},
success: function(res) {
console.log(res)
}
})

三、后台获取cookie中的PHPSESSID,将PHPSESSID当作session_id使用

<?php
// 判断$_COOKIE['PHPSESSID']是否存在,存在则作session_id使用
if ($_COOKIE['PHPSESSID']) {
session_id($_COOKIE['PHPSESSID']);
}

session_start();
echo session_id();


原文链接:https://blog.csdn.net/qq_41654694/article/details/85991846

收起阅读 »

vue 重复点击菜单,路由重复报错

报错信息vue-router在3.0版本以上时,重复点菜单,控制台会报错,虽然不影响使用,但是最好处理下这个问题,不然也可能会影响调试其他问题。报错原因vue-router在3.0版本以上时 ,回调形式改成了promise api,返回的是promise,如果...
继续阅读 »

报错信息

vue-router在3.0版本以上时,重复点菜单,控制台会报错,虽然不影响使用,但是最好处理下这个问题,不然也可能会影响调试其他问题。


报错原因
vue-router在3.0版本以上时 ,回调形式改成了promise api,返回的是promise,如果没有捕获到错误,控制台始终会出现如上图的报错
node_module/vue-router/dist/vue-router.js 搜VueRouter.prototype.push

解决方法

1.降低vue-router的版本

npm i vue-router@3.0 -S

2.在vue.use(Router)使用路由插件之前插入如下代码

//获取原型对象上的push函数
const originalPush = Router.prototype.push
//修改原型对象中的push方法
Router.prototype.push = function push (location) {
return originalPush.call(this, location).catch(err => err)
}

3.捕获异常

// 捕获router.push异常
this.$router.push(route).catch(err => {
console.log('输出报错',err)

4.补齐router第三个参数

// 补齐router.push()的第三个参数
this.$router.push(route, () => {}, (e) => {
console.log('输出报错',e)
})

本文链接:https://blog.csdn.net/pinbolei/article/details/115620529


收起阅读 »

深入理解vue中的slot与slot-scope

写在前面vue中关于插槽的文档说明很短,语言又写的很凝练,再加上其和methods,data,computed等常用选项使用频率、使用先后上的差别,这就有可能造成初次接触插槽的开发者容易产生“算了吧,回头再学,反正已经可以写基础组件了”,于是就关闭了vue说明...
继续阅读 »

写在前面

vue中关于插槽的文档说明很短,语言又写的很凝练,再加上其和methods,data,computed等常用选项使用频率、使用先后上的差别,这就有可能造成初次接触插槽的开发者容易产生“算了吧,回头再学,反正已经可以写基础组件了”,于是就关闭了vue说明文档。

实际上,插槽的概念很简单,下面通过分三部分来讲。这个部分也是按照vue说明文档的顺序来写的。

进入三部分之前,先让还没接触过插槽的同学对什么是插槽有一个简单的概念:插槽,也就是slot,是组件的一块HTML模板,这块模板显示不显示、以及怎样显示由父组件来决定。 实际上,一个slot最核心的两个问题这里就点出来了,是显示不显示怎样显示

由于插槽是一块模板,所以,对于任何一个组件,从模板种类的角度来分,其实都可以分为非插槽模板插槽模板两大类。
非插槽模板指的是html模板,指的是‘div、span、ul、table’这些,非插槽模板的显示与隐藏以及怎样显示由插件自身控制;插槽模板是slot,它是一个空壳子,因为它显示与隐藏以及最后用什么样的html模板显示由父组件控制。但是插槽显示的位置确由子组件自身决定,slot写在组件template的哪块,父组件传过来的模板将来就显示在哪块

单个插槽 | 默认插槽 | 匿名插槽

首先是单个插槽,单个插槽是vue的官方叫法,但是其实也可以叫它默认插槽,或者与具名插槽相对,我们可以叫它匿名插槽。因为它不用设置name属性。

单个插槽可以放置在组件的任意位置,但是就像它的名字一样,一个组件中只能有一个该类插槽。相对应的,具名插槽就可以有很多个,只要名字(name属性)不同就可以了。

下面通过一个例子来展示。

父组件:

<template>
<div class="father">
<h3>这里是父组件</h3>
<child>
<div class="tmpl">
<span>菜单1</span>
<span>菜单2</span>
<span>菜单3</span>
<span>菜单4</span>
<span>菜单5</span>
<span>菜单6</span>
</div>
</child>
</div>
</template>

子组件:

<template>
<div class="child">
<h3>这里是子组件</h3>
<slot></slot>
</div>
</template>

在这个例子里,因为父组件在<child></child>里面写了html模板,那么子组件的匿名插槽这块模板就是下面这样。也就是说,子组件的匿名插槽被使用了,是被下面这块模板使用了。

<div class="tmpl">
<span>菜单1</span>
<span>菜单2</span>
<span>菜单3</span>
<span>菜单4</span>
<span>菜单5</span>
<span>菜单6</span>
</div>

最终的渲染结果如图所示:


注:所有demo都加了样式,以方便观察。其中,父组件以灰色背景填充,子组件都以浅蓝色填充。

具名插槽

匿名插槽没有name属性,所以是匿名插槽,那么,插槽加了name属性,就变成了具名插槽。具名插槽可以在一个组件中出现N次。出现在不同的位置。下面的例子,就是一个有两个具名插槽单个插槽的组件,这三个插槽被父组件用同一套css样式显示了出来,不同的是内容上略有区别。

父组件:

<template>
<div class="father">
<h3>这里是父组件</h3>
<child>
<div class="tmpl" slot="up">
<span>菜单1</span>
<span>菜单2</span>
<span>菜单3</span>
<span>菜单4</span>
<span>菜单5</span>
<span>菜单6</span>
</div>
<div class="tmpl" slot="down">
<span>菜单-1</span>
<span>菜单-2</span>
<span>菜单-3</span>
<span>菜单-4</span>
<span>菜单-5</span>
<span>菜单-6</span>
</div>
<div class="tmpl">
<span>菜单->1</span>
<span>菜单->2</span>
<span>菜单->3</span>
<span>菜单->4</span>
<span>菜单->5</span>
<span>菜单->6</span>
</div>
</child>
</div>
</template>

子组件:

<template>
<div class="child">
// 具名插槽
<slot name="up"></slot>
<h3>这里是子组件</h3>
// 具名插槽
<slot name="down"></slot>
// 匿名插槽
<slot></slot>
</div>
</template>

显示结果如图:



可以看到,父组件通过html模板上的slot属性关联具名插槽。没有slot属性的html模板默认关联匿名插槽。

作用域插槽 | 带数据的插槽

最后,就是我们的作用域插槽。这个稍微难理解一点。官方叫它作用域插槽,实际上,对比前面两种插槽,我们可以叫它带数据的插槽。什么意思呢,就是前面两种,都是在组件的template里面写

匿名插槽
<slot></slot>
具名插槽
<slot name="up"></slot>

但是作用域插槽要求,在slot上面绑定数据。也就是你得写成大概下面这个样子。

<slot name="up" :data="data"></slot>
export default {
data: function(){
return {
data: ['zhangsan','lisi','wanwu','zhaoliu','tianqi','xiaoba']
}
},
}

我们前面说了,插槽最后显示不显示是看父组件有没有在child下面写模板,像下面那样。

<child>
html模板
</child>

写了,插槽就总得在浏览器上显示点东西,东西就是html该有的模样,没写,插槽就是空壳子,啥都没有。
OK,我们说有html模板的情况,就是父组件会往子组件插模板的情况,那到底插一套什么样的样式呢,这由父组件的html+css共同决定,但是这套样式里面的内容呢?

正因为作用域插槽绑定了一套数据,父组件可以拿来用。于是,情况就变成了这样:样式父组件说了算,但内容可以显示子组件插槽绑定的。

我们再来对比,作用域插槽和单个插槽和具名插槽的区别,因为单个插槽和具名插槽不绑定数据,所以父组件是提供的模板要既包括样式由包括内容的,上面的例子中,你看到的文字,“菜单1”,“菜单2”都是父组件自己提供的内容;而作用域插槽,父组件只需要提供一套样式(在确实用作用域插槽绑定的数据的前提下)。

下面的例子,你就能看到,父组件提供了三种样式(分别是flex、ul、直接显示),都没有提供数据,数据使用的都是子组件插槽自己绑定的那个人名数组。

父组件:

<template>
<div class="father">
<h3>这里是父组件</h3>
<!--第一次使用:用flex展示数据-->
<child>
<template slot-scope="user">
<div class="tmpl">
<span v-for="item in user.data">{{item}}</span>
</div>
</template>

</child>

<!--第二次使用:用列表展示数据-->
<child>
<template slot-scope="user">
<ul>
<li v-for="item in user.data">{{item}}</li>
</ul>
</template>

</child>

<!--第三次使用:直接显示数据-->
<child>
<template slot-scope="user">
{{user.data}}
</template>

</child>

<!--第四次使用:不使用其提供的数据, 作用域插槽退变成匿名插槽-->
<child>
我就是模板
</child>
</div>
</template>

子组件:

<template>
<div class="child">

<h3>这里是子组件</h3>
// 作用域插槽
<slot :data="data"></slot>
</div>
</template>

export default {
data: function(){
return {
data: ['zhangsan','lisi','wanwu','zhaoliu','tianqi','xiaoba']
}
}
}

结果如图所示:



github

以上三个demo就放在GitHub了,有需要的可以去取。使用非常方便,是基于vue-cli搭建工程。

https://github.com/cunzaizhuyi/vue-slot-demo

转载地址:https://segmentfault.com/a/1190000012996217

收起阅读 »

JavaScript 逐点突破系列 -- 变幻莫测的this指向

JavaScript 逐点突破系列 – 变幻莫测的this指向this指向事件调用环境谁触发事件,函数里面的this指向就是谁let button = document.getElemetById('button')button.onclick = funct...
继续阅读 »

JavaScript 逐点突破系列 – 变幻莫测的this指向

this指向

事件调用环境

谁触发事件,函数里面的this指向就是谁

let button = document.getElemetById('button')
button.onclick = function () {
console.log(this) //button对象
}

全局环境
浏览器环境下

console.log(this) // window

node环境下

console.log(this) // module.exports

函数内部

this最终指向的是调用的对象,和声明没有直接关系

var object = {
name: 'object',
getName: function() {
console.log(this)
}
}
var bar = object.getName // 只是函数声明并未调用
object.getName() // object对象
window.object.getName() // object对象
/* 函数被多层对象所包含,如果函数被最外层对象调用,this指向
的也只是它上一级的对象。*/
bar() // window对象

构造函数

构造函数中的this指向的是实例对象

let fn = function(){
this.id = 'xiaoMing'
console.log(this.id)
}
let fn1 = new fn() //this指向fn1对象

new 的内部原理

【1】创建一个空对象 obj;
【2】把 child 的__proto__ 指向构造函数 parent 的原型对象 prototype,此时便建立了 obj 对象的原型链:child ->parent.prototype->Object.prototype->null
【3】在 child 对象的执行环境调用 parent 函数并传递参数。
【4】考察第 3 步的返回值,如果无返回值 或者 返回一个非对象值,则将 child 作为新对象返回;否则会将 result 作为新对象返回。this绑定的是返回的对象。

function fn(){
this.num = 10;
}
fn.num = 20;
fn.prototype.num = 30;
fn.prototype.method = function() {
console.log(this.num);
}
var prototype = fn.prototype
var method = prototype.method
new fn().method() // 10
prototype.method() // 30
method() // undefined

箭头函数

箭头函数本身没有this和arguments,箭头函数继承上下文的this关键字,也就是说指向上一层作用域的this。
注意点

  1. 函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象
function foo() {
return () => {
return () => {
return () => {
console.log('id:', this.id);
};
};
};
}

var f = foo.call({id: 1});
var t1 = f.call({id: 2})()(); // id: 1
var t2 = f().call({id: 3})(); // id: 1
var t3 = f()().call({id: 4}); // id: 1

上面代码之中,只有一个this,就是函数foo的this,所以t1、t2、t3都输出同样的结果。因为所有的内层函数都是箭头函数,都没有自己的this,它们的this其实都是最外层foo函数的this。

除了this,以下三个变量在箭头函数之中也是不存在的,指向外层函数的对应变量:arguments、super、new.target。

由于箭头函数没有自己的this,所以当然也就不能用call()、apply()、bind()这些方法去改变this的指向。

不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
不可以使用yield命令,因此箭头函数不能用作 Generator 函数。


修改this指向

apply、call、bind

用法

func.call(this, arg1, arg2);
func.apply(this, [arg1, arg2])
func.bind(this, arg1, arg2)()

apply、call的区别

call 需要把参数按顺序传递进去,而 apply 则是把参数放在数组里。

bind和其他两种方法的区别

bind 是返回对应函数,便于稍后调用;apply 、call 则是立即调用 。

原文链接:https://blog.csdn.net/weixin_45495667/article/details/108801000




收起阅读 »

js实现函数防抖节流

一、什么是函数防抖跟节流?函数防抖: 在事件被触发n秒之后在执行回调函数,如果在n秒内又被触发 ,则重新计时。函数节流: 规定一个单位时间,规定在这个时间内,只能执行一次回调函数,如果在这个时间内呗触发多次,则只有一次失效。表现形式就是它有...
继续阅读 »

一、什么是函数防抖跟节流?

函数防抖: 在事件被触发n秒之后在执行回调函数,如果在n秒内又被触发 ,则重新计时。
函数节流: 规定一个单位时间,规定在这个时间内,只能执行一次回调函数,如果在这个时间内呗触发多次,则只有一次失效。表现形式就是它有自己的一个执行频率。

二、JavaScript实现

1.函数防抖

代码如下(示例):

function debounce(callback, wait) {
let timer = null;
return function(){
let _this = this,
arg = arguments;
if(!!timer){
clearTimeout(timer)
timer = null
}
timer = setTimeout(function(){
callback.call(_this,arg)
},wait)
}
}
let _debounce = debounce(function() {
console.log('dshdihdi')
}, 1000)


2.函数节流

代码如下(示例):

// 函数节流,时间戳版本
function throttle(callback, wait) {
let time = new Date().getTime();
return function() {
let _this = this,
arg = arguments;
let nowTime = new Date().getTime();
if (nowTime - time >= wait){
callback.call(_this,...arg)
time = nowTime
}
}
}
let _throttle = throttle(function(e) {
console.log(e)
console.log(arguments)
}, 1000)


函数节流: 懒加载分页请求资源、音乐播放进度条更新等等

函数防抖: 频繁操作点赞、登录注册、需要提交最新信息等等

原文链接:https://blog.csdn.net/qq_45924621/article/details/115586112

收起阅读 »

JavaScript new 操作符

new 操作符做的事情 - 01创建了一个全新的对象。将对象链接到这个函数的 prototype 对象上。执行构造函数,并将 this 绑定到新创建的对象上。判断构造函数执行返回的结果是否是引用数据类型,若是则返回构造函数执行的结果,否则返回创建的对象。new...
继续阅读 »
new 操作符做的事情 - 01
  1. 创建了一个全新的对象。
  2. 将对象链接到这个函数的 prototype 对象上。
  3. 执行构造函数,并将 this 绑定到新创建的对象上。
  4. 判断构造函数执行返回的结果是否是引用数据类型,若是则返回构造函数执行的结果,否则返回创建的对象。

new 操作符做的事情 - 02

  1. 创建一个全新的对象 (无原型的Object.create(null)) 。
  2. 目的是保存 new 出来的实例的所有属性。
  3. 将构造函数的原型赋值给新创建的对象的原型。
  4. 目的是将构造函数原型上的属性继承下来。
  5. 调用构造函数,并将 this 指向新建的对象。
  6. 目的是让构造函数内的属性全部转交到该对象上,使得 this 指向改变,方法有三 : apply、call、bind 。
  7. 判断构造函数调用的方式,如果是 new 的调用方式,则返回经过加工后的新对象,如果是普通调用方式,则直接返回构造函数调用时的返回值。
function myNew(Constructor, ...args) {
// 判断 Constructor 参数是否是函数
if (typeof Constructor !== 'function') {
return 'Constructor.apply is not a function';
};

// 1、创建了一个全新的对象。
let newObject = {};

// 2、将对象链接到这个函数的 prototype 对象上。
newObject.__proto__ = Constructor.prototype;

// 此处是把 1 / 2 步结合到一起
// const newObject = Object.create(Constructor.prototype);

// 3、执行构造函数,
// 并将 this 绑定到新创建的对象上。
let result = Constructor.apply(newObject, args);

// 4. 判断构造函数执行返回的结果是否是引用数据类型,
// 若是则返回构造函数执行的结果,
// 否则返回创建的对象。
if ((result !== null && typeof result === 'object') || (typeof result === 'function')) {
return result;
} else {
return newObject;
};
};

// 需要被 new 的函数
function NewTest(args) {
this.dataValue = args;
return this;
};

// 定义参数
let dataObj = {
sname: '杨万里',
number: 3,
web: 'vue'
};
let dataArray = [5, 'uniApp', '范仲淹'];

// 执行 myNew 函数
let test = myNew(NewTest, 1, 'JavaScript', '辛弃疾', dataObj, dataArray);
console.log(test); // NewTest {dataValue: Array(5)}


本文链接:https://blog.csdn.net/weixin_51157081/article/details/115577751


收起阅读 »

微信小程序-使用canvas绘制图片,下载,分享

接下来下选择图片// 点击选择图片按钮触发start: function() { let that = this let ctx = wx.createCanvasContext('myCanvas') // 设置canvas背景色, 否则制...
继续阅读 »

需求, 使用canvas绘制图片和文字, 生成图片

<!--pages/canvas/canvas.wxml-->
<view>
<view class="container">
<button bind:tap="start" class="start" size="mini">选择图片</button>
<button bind:tap="downloadCanvas" class="downloadCanvas" size="mini">下载</button>
<button bind:tap="share" class="share" size="mini">分享</button>
<!-- 分享图片功能 -->
</view>
<text>canvas区域</text>
<canvas style='width:{{canvasWidth}}px;height:{{canvasHeight}}px;border: 1px solid grey;' canvas-id='myCanvas'></canvas>
<image v-if="previewImage" :src="{{canvasImage}}"></image>
</view>


图片功能

先设置canvas区域的宽度和高度

js代码

data: {
title: 'canvas绘制图片',
canvasWidth: '', // canvas宽度
canvasHeight: '', // canvas高度
imagePath: '', // 分享的图片路径
leftMargin: 0,
topMargin: 0,
imgInfo: {},
ctx: [],
canvasImage: '',
previewImage: false,
imgProportion: 0.8, // 图片占canvas画布宽度百分比
imgToTop: 100 // 图片到canvas顶部的距离
},
onLoad: function(options) {
var that = this
var sysInfo = wx.getSystemInfo({
success: function(res) {
that.setData({
canvasWidth: res.windowWidth,
// 我这里选择canvas高度是系统高度的80%
canvasHeight: res.windowHeight * 0.8
})
// 根据图片比例, 使图片居中
let leftMargin = (res.windowWidth * (1 - that.data.imgProportion)) / 2
that.setData({
leftMargin
})
}
})
}


接下来下选择图片

// 点击选择图片按钮触发
start: function() {
let that = this
let ctx = wx.createCanvasContext('myCanvas')
// 设置canvas背景色, 否则制作的图片是透明的
ctx.setFillStyle('#f8f8f8')
ctx.fillRect(0, 0, that.data.canvasWidth, that.data.canvasHeight)
this.addImage(ctx)
},
// 添加图片
addImage: function(ctx) {
var that = this;
let imgInfo = that.data.imgInfo
var path
wx.chooseImage({
count: '1',
success(res) {
wx.getImageInfo({
src: res.tempFilePaths[0],
success: function(response) {
// 返回的response里有图片的临时路径和图片信息(高度/宽度)
that.setData({
imgInfo: response,
path: response.path
})
that.drawImage(ctx)
}
})
}
})
this.addTitle(ctx)
},


绘制文字和图

// 绘制图片
drawImage(ctx) {
let that = this
let imgInfo = that.data.imgInfo
let path = that.data.path
// 计算图片宽度 宽度固定 高度等比缩放
let imgWidth = that.data.canvasWidth * that.data.imgProportion
let imgHeight = imgInfo.height / imgInfo.width * imgWidth
// drawImage参数, 下面会说明
ctx.drawImage(path, 0, 0, imgInfo.width, imgInfo.height, that.data.leftMargin, that.data.imgToTop, imgWidth, imgHeight)
ctx.draw()
that.data.previewImage = true
},
//绘制文字
addTitle: function(ctx) {
var str = this.data.title
ctx.font = 'normal bold 16px sans-serif';
ctx.setTextAlign('center'); // 文字居中
ctx.setFillStyle("#222222");
ctx.fillText(str, this.data.canvasWidth / 2, 45) // 文字位置
},


drawImage方法


drawImage 方法允许在 canvas 中插入其他图像( img 和 canvas 元素) 。drawImage函数有三种函数原型:


drawImage(image, dx, dy) 在画布指定位置绘制原图

drawImage(image, dx, dy, dw, dh) 在画布指定位置上按原图大小绘制指定大小的图

drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh) 剪切图像,并在画布上定位被剪切的部分 从 1.9.0 起支持


参数描述

image

所要绘制的图片资源(网络图片要通过 getImageInfo / downloadFile 先下载)

s

需要绘制到画布中的,image的矩形(裁剪)选择框的左上角 x 坐标

sy

需要绘制到画布中的,image的矩形(裁剪)选择框的左上角 y 坐标

sWidth

需要绘制到画布中的,image的矩形(裁剪)选择框的宽度

sHeight

需要绘制到画布中的,image的矩形(裁剪)选择框的高度

dx

image的左上角在目标 canvas 上 x 轴的位置

dy

image的左上角在目标 canvas 上 y 轴的位置

dWidth

在目标画布上绘制imageResource的宽度,允许对绘制的image进行缩放

dHeight

在目标画布上绘制imageResource的高度,允许对绘制的image进行缩放


下载图片

//点击下载按钮保存canvas图片
downloadCanvas: function() {
let that = this;
// 判断用户是否选择了图片
if (that.data.previewImage) {
wx.canvasToTempFilePath({
x: 0,
y: 0,
width: that.canvasWidth,
height: that.canvasWidth,
destWidth: that.canvasWidth,
destHeight: that.canvasHeight,
canvasId: 'myCanvas',
success: function success(res) {
wx.saveImageToPhotosAlbum({
filePath: res.tempFilePath,
success(res) {
console.log(res, '保存')
}
})
}
});
} else {
wx.showToast({
title: '请先选择图片',
image: '../../static/img/error.png'
})
}
}


分享图片

// 分享图片
share() {
let that = this
if (that.data.previewImage) {

wx.canvasToTempFilePath({
x: 0,
y: 0,
width: that.canvasWidth,
height: that.canvasWidth,
destWidth: that.canvasWidth,
destHeight: that.canvasHeight,
canvasId: 'myCanvas',
success: function success(res) {
wx.showToast({
icon: 'none',
title: '长按图片分享',
duration: 1500
})
setTimeout(
() => {
wx.previewImage({
urls: [res.tempFilePath]
})
}, 1000)
}
})
} else {
wx.showToast({
title: '请先选择图片',
image: '../../static/img/error.png'
})
}
}



原文链接:https://www.jianshu.com/p/1e54146b8a26

收起阅读 »

H5之外部浏览器唤起微信分享

转自https://blog.csdn.net/qq_18976087/article/details/79095735最近在做一个手机站,要求点击分享可以直接打开微信分享出去。而不是jiathis,share分享这种的点击出来二维码。在网上看了很多,都说AP...
继续阅读 »

转自https://blog.csdn.net/qq_18976087/article/details/79095735

最近在做一个手机站,要求点击分享可以直接打开微信分享出去。而不是jiathis,share分享这种的点击出来二维码。在网上看了很多,都说APP能唤起微信,手机网页实现不了。也找了很多都不能直接唤起微信。

总结出来一个可以直接唤起微信的。适应手机qq浏览器和uc浏览器。

下面上代码,把这些直接放到要转发的页面里就可以了:

html部分:

<script src="mshare.js"></script>//引进mshare.js
<button data-mshare="0">点击弹出原生分享面板</button>
<button data-mshare="1">点击触发朋友圈分享</button>
<button data-mshare="2">点击触发发送给微信朋友</button>


js部分:

<script>
var mshare = new mShare({
title: 'Lorem ipsum dolor sit.',
url: 'http://m.ly.com',
desc: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Quaerat inventore minima voluptates.',
img: 'http://placehold.it/150x150'
});
$('button').click(function () {
// 1 ==> 朋友圈 2 ==> 朋友 0 ==> 直接弹出原生
mshare.init(+$(this).data('mshare'));
});
</script>


/**
* 此插件主要作用是在UC和QQ两个主流浏览器
* 上面触发微信分享到朋友圈或发送给朋友的功能
*/
'use strict';
var UA = navigator.appVersion;

/**
* 是否是 UC 浏览器
*/
var uc = UA.split('UCBrowser/').length > 1 ? 1 : 0;

/**
* 判断 qq 浏览器
* 然而qq浏览器分高低版本
* 2 代表高版本
* 1 代表低版本
*/
var qq = UA.split('MQQBrowser/').length > 1 ? 2 : 0;

/**
* 是否是微信
*/
var wx = /micromessenger/i.test(UA);

/**
* 浏览器版本
*/
var qqVs = qq ? parseFloat(UA.split('MQQBrowser/')[1]) : 0;
var ucVs = uc ? parseFloat(UA.split('UCBrowser/')[1]) : 0;

/**
* 获取操作系统信息 iPhone(1) Android(2)
*/
var os = (function () {
var ua = navigator.userAgent;

if (/iphone|ipod/i.test(ua)) {
return 1;
} else if (/android/i.test(ua)) {
return 2;
} else {
return 0;
}
}());

/**
* qq浏览器下面 是否加载好了相应的api文件
*/
var qqBridgeLoaded = false;

// 进一步细化版本和平台判断
if ((qq && qqVs < 5.4 && os == 1) || (qq && qqVs < 5.3 && os == 1)) {
qq = 0;
} else {
if (qq && qqVs < 5.4 && os == 2) {
qq = 1;
} else {
if (uc && ((ucVs < 10.2 && os == 1) || (ucVs < 9.7 && os == 2))) {
uc = 0;
}
}
}
/**
* qq浏览器下面 根据不同版本 加载对应的bridge
* @method loadqqApi
* @param {Function} cb 回调函数
*/
function loadqqApi(cb) {
// qq == 0
if (!qq) {
return cb && cb();
}
var script = document.createElement('script');
script.src = (+qq === 1) ? '//3gimg.qq.com/html5/js/qb.js' : '//jsapi.qq.com/get?api=app.share';
/**
* 需要等加载过 qq 的 bridge 脚本之后
* 再去初始化分享组件
*/
script.onload = function () {
cb && cb();
};
document.body.appendChild(script);
}
/**
* UC浏览器分享
* @method ucShare
*/
function ucShare(config) {
// ['title', 'content', 'url', 'platform', 'disablePlatform', 'source', 'htmlID']
// 关于platform
// ios: kWeixin || kWeixinFriend;
// android: WechatFriends || WechatTimeline
// uc 分享会直接使用截图
var platform = '';
var shareInfo = null;
// 指定了分享类型
if (config.type) {
if (os == 2) {
platform = config.type == 1 ? 'WechatTimeline' : 'WechatFriends';
} else if (os == 1) {
platform = config.type == 1 ? 'kWeixinFriend' : 'kWeixin';
}
}
shareInfo = [config.title, config.desc, config.url, platform, '', '', ''];
// android
if (window.ucweb) {
ucweb.startRequest && ucweb.startRequest('shell.page_share', shareInfo);
return;
}
if (window.ucbrowser) {
ucbrowser.web_share && ucbrowser.web_share.apply(null, shareInfo);
return;
}
}
/**
* qq 浏览器分享函数
* @method qqShare
*/
function qqShare(config) {
var type = config.type;
//微信好友 1, 微信朋友圈 8
type = type ? ((type == 1) ? 8 : 1) : '';
var share = function () {
var shareInfo = {
'url': config.url,
'title': config.title,
'description': config.desc,
'img_url': config.img,
'img_title': config.title,
'to_app': type,
'cus_txt': ''
};
if (window.browser) {
browser.app && browser.app.share(shareInfo);
} else if (window.qb) {
qb.share && qb.share(shareInfo);
}
};
if (qqBridgeLoaded) {
share();
} else {
loadqqApi(share);
}
}
/**
* 对外暴露的接口函数
* @method mShare
* @param {Object} config 配置对象
*/
function mShare(config) {
this.config = config;
this.init = function (type) {
if (typeof type != 'undefined') this.config.type = type;
try {
if (uc) {
ucShare(this.config);
} else if (qq && !wx) {
qqShare(this.config);
}
} catch (e) {}
}
}
// 预加载 qq bridge
loadqqApi(function () {
qqBridgeLoaded = true;
});
if (typeof module === 'object' && module.exports) {
module.exports = mShare;
} else {
window.mShare = mShare;
}
收起阅读 »