于谦

于谦 查看完整档案

填写现居城市  |  填写毕业院校  |  填写所在公司/组织填写个人主网站
编辑
_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 该用户太懒什么也没留下

个人动态

于谦 提出了问题 · 3月16日

解决js中 关于class的一个问题

问题示例如下:
正确:
image.png
错误:
image.png

希望的结果:
错误原因我知道因为double()是在外部调用的,this指向了外部而不是num实例对象内,怎样才能让错误示例中的double能够拿到正确的this,我希望通过这种写法实现

关注 5 回答 5

于谦 提出了问题 · 3月15日

解决react 如果刷新整个视图 但是组件不产生变化会影响性能吗

问题示例:

function Parent(){
    const [state, setState]=useState(0)
    
    const handleClick=()=>setState(state+1)
    
    return <div>
        <button onClick={handleClick}>点我刷新Parent状态</button>
        <Child1/>
        <Child2/>
        <Child3/>
    </div
}
  1. 点击按钮后
  2. 整个Parent组件 以及3个子组件都会刷新视图
  3. 但是子组件自身没有产生任何变化

请问这样会影响性能吗?

关注 5 回答 2

于谦 提出了问题 · 3月9日

解决微信小程序如何子组件改变父组件数据(状态)?

  1. 假设父组件有一个数据 count
  2. 子组件为list
  3. list组件包含子组件listItem
  4. 那么子子组件listItem如何来修改父组件的数据呢?

按照官网的父子组件数据传递方式,是通过bind:myevent传递自定义事件的方式来修改数据,但是遇到子子组件、多代子组件的情况,这种方式代码量非常冗余,而且可读性差,有没有类似react中可以直接通过prop传递一个方法的方式修改上层数据?或者类似redux provider这样的全局数据管理容器?

关注 3 回答 2

于谦 收藏了文章 · 2019-09-12

JS实现将图片复制到剪贴板

前言

最近项目新增需求:用户能够拖拽页面上的图片文件到word文档。
当操作浏览器里拖拽图片至别的程序,在word文档中展示出获取到的只是图片的url地址,而非预期的图片文件。在现有的拖拽事件所提供api无法满足需求的情况下,换一个思路走:尝试将图片复制到剪贴板

对于原生js的复制操作,已有封装好的库clipboard.js,但是封装得太死,无法满足更多定制化的需求,主要表现在以下两点:

  1. 只接受click事件,无法绑定其他事件。
  2. 只复制目标节点的子节点,对于img标签,如果不额外包裹一层父元素,无法实现图片复制。

参考clipboard.js源码,了解了实现原理后(其实非常简单!:) ),我们就能自己动手封装一个复制方法:

概述

Range对象

Range表示包含节点和部分文本节点的文档片段。最常见的就是用户在浏览器拖动鼠标选择的内容(user selection)

clipboard.png

比如上图这块蓝色高亮区域。

在现代浏览器中(IE9以上),你可以通过Document.createRange()方法或者new Range()创建一个Range对象;当需要获取user selection时,你应该使用window.getSelection()方法获取Selection对象

有点懵?

clipboard.png

刚了解了Range对象,而Selection对象又是什么?阅读了文档之后,还是疑惑它们之间的区别

clipboard.png
Selection对象表示用户的选择,而Range对象则表示文档的连续部分,与任何视觉表示无关。一个Selection对象几乎可由0到多个Range表示出来,当然,Range对象也能独立于Selection而被完全的创建和修改。

简单的演示代码

html部分:

<p>
  这是一段文字
  AAAAAAAAAA
  BBBBBBBBBB
  <span id="range">Range</span>
</p>
<input id="input" type="text">
<button id="button1">选择文字后点击</button>
<button id="button2">点击后将选中指定的节点</button>

js部分:

var btn1 = document.getElementById('button1'),
    btn2 = document.getElementById('button2'),
    input = document.getElementById('input'),
    rangespan = document.getElementById('range');
    
var selection = window.getSelection(),
    range = document.createRange();
btn1.addEventListener("click", function(event) {
    input.value = selection.toString();
}); 
btn2.addEventListener("click", function(event) {
    range.selectNode(rangespan);
    selection.removeAllRanges(); //删除包含在selection原本的range,也就是取消用户选中的范围
    selection.addRange(range); //让选中部分变成我们自己定义的节点内容
}); 

演示地址

https://jsfiddle.net/muvhqcnf...

一点就会 :)

兼容陈旧IE版本

Microsoft提供了类似的TextRange接口。
在实际代码部分会展示Microsoft TextRange的基本使用。

execCommand方法

execCommand方法允许运行命令来操纵可编辑区域的内容。该方法的第一个参数是命令的名称,参数类型为DOMString。
在这里,我们将利用execCommand方法的copy命令实现复制选中的内容:

document.execCommand('copy')

execCommand API起源于IE,后来被添加到HTML5(HTML Editing APIs),在各浏览器的表现会有不同。更多请查看文档

我们回到前面的演示代码,将btn1的点击事件替换成execCommand命令:

btn1.addEventListener("click", function(event) {
    //input.value = selection.toString();
    document.execCommand('copy');
}); 

拖动鼠标选择文字,点击按钮后看看能粘贴出什么:)

复制图片功能的具体实现

封装可以兼容ie的getSelect方法

还记得前面的例子里,我们通过rangeselectNode(node)方法获取节点, 再使用selectionremoveAllRanges()方法和addRange(range)将节点替换我们获取的节点。在这里,我们同样可以这样选中我们目标的img节点:


const getSelect = targetNode => {
  if (window.getSelection) {
    //chrome等主流浏览器
    var selection = window.getSelection();
    var range = document.createRange();
    range.selectNode(targetNode);
    selection.removeAllRanges();
    selection.addRange(range);
  } else if (document.body.createTextRange) {
    //ie
    var range = document.body.createTextRange();
    range.moveToElementText(targetNode);
    range.select();
  }
}

派发事件

为了不浪费性能,我们使用事件委托到希望被复制的节点上。这里对传入的nodeName进行处理,方便自由的控制被复制一个或多个节点类型。默认为<img>。

const clipboardHandler = (nodeName, event) => {
  event = event || nodeName; //不传参时
  const type = Object.prototype.toString.call(nodeName).replace(/\[object\s|\]/g, '');
  const target = event.target || event.srcElement;

  var result = false;
  switch (type) {
    case 'String':
      result = (target.nodeName.toLowerCase() === nodeName);
      break;
    case 'Array':
      result = nodeName.some(item => target.nodeName.toLowerCase() === item);
      break;
    case 'Object':
      nodeName = null;
    default:
      result = (target.nodeName === 'IMG');
  }

  if (result) {
    //调用之前封装好的getSelect方法
    getSelect(target);
    document.execCommand('copy');
  }
}

调用:

[element].addEventListener('mousedown', clipboardHandler); //预备拖动图片按下鼠标时执行复制

传递参数(字符串或数组):

var [somename]Handler = clipboardHandler.bind(null, [nodeName]);
[element].addEventListener([eventType],[somename]Handler);

完整代码演示地址

已验证在chrome和ie8上可行(ie8需要对es6语法与bind和addEventListener方法进行pollyfill)

希望能够帮助到你:)

查看原文

于谦 赞了回答 · 2019-08-30

解决JS关于async await一个巨难的问题

问题提的确实符合你的名字,感觉在说相声

关注 4 回答 3

于谦 提出了问题 · 2019-08-26

解决JS关于async await一个巨难的问题

代码如下

// 构造一个Promise对象
function timeout(ms){
  return new Promise((res,rej)=>{
    setTimeout(()=>{
      res()
    },ms)
  })
}

// 假定一个数组
const arr = [1,2,3]

// async函数
async function run(){
  arr.forEach(async ()=>{
    await timeout(1000)
  })
  console.log( '3秒过去了' )
}

run()

我希望‘3秒过去了’应该在3秒后打印出来,而上述代码运行后会瞬间打印

看下哪位大神能最先解决我的这个难题,以最优解者为胜,若差不多则以最先解出者为胜。

前提条件:必须使用arr.forEach,必须使用asyncawait

关注 4 回答 3

于谦 收藏了文章 · 2019-08-21

Vue登录注册,并保持登录状态

关于vue登录注册,并保持登录状态,是vue玩家必经之路,网上也有很多的解决方法,但是有一些太过于复杂,新手可能会看的一脸懵逼,现在给大家介绍一种我自己写项目在用而且并不难理解的一种方法。

项目中有一些路由是需要登录才可以进入的,比如首页,个人中心等等
有一些路由是不需要登录就可以进入,比如登录页,注册页,忘记密码等等
那如何判断路由是否需要登录呢?就要在路由JS里面做文章

在router.js中添加meta区分

比如登录注册页面,不需要登录即可进入,那么我们把meta中的isLogin标志设置为false

{
  //登录
  path: '/login',
  component: login,
  meta: {
    isLogin: false
  }
},
{
  //注册
  path: '/register',
  component: register,
  meta: {
    isLogin: false
  }
},

而在首页我们需要登录才能进入,那么我们把meta中的isLogin标志设置为true

{
  //首页
  path: '/home',
  component: home,
  meta: {
    isLogin: true
  },
}

这样我们就为进入各个路由是否需要登录做了区分。

接下来我们在login.vue中修改登录后状态

我们使用axios向后台发起登录请求

this.$axios.post("/xxx/login", {user:name,password:pwd})
    .then(data => {
        //登录失败,先不讨论
        if (data.data.status != 200) {
          //iViewUi的友好提示
          this.$Message.error(data.data.message);
        //登录成功
        } else {
          //设置Vuex登录标志为true,默认userLogin为false
          this.$store.dispatch("userLogin", true);
          //Vuex在用户刷新的时候userLogin会回到默认值false,所以我们需要用到HTML5储存
          //我们设置一个名为Flag,值为isLogin的字段,作用是如果Flag有值且为isLogin的时候,证明用户已经登录了。
          localStorage.setItem("Flag", "isLogin");
          //iViewUi的友好提示
          this.$Message.success(data.data.message);
          //登录成功后跳转到指定页面
          this.$router.push("/home");
        }
 });

Vuex里面我是这样写的(如果项目不需要Vuex,那么直接使用HTML5储存就可以了):

export const store = new Vuex.Store({
  // 设置属性
  state: {
    isLogin: false,
  },

  // 获取属性的状态
  getters: {
    //获取登录状态
    isLogin: state => state.isLogin,
  },

  // 设置属性状态
  mutations: {
    //保存登录状态
    userStatus(state, flag) {
      state.isLogin = flag
    },
  },

  // 应用mutations
  actions: {
    //获取登录状态
    userLogin({commit}, flag) {
      commit("userStatus", flag)
    },
  }
})

重点来了~,在mian.js里

router.beforeEach((to, from, next) => {

  //获取用户登录成功后储存的登录标志
  let getFlag = localStorage.getItem("Flag");

  //如果登录标志存在且为isLogin,即用户已登录
  if(getFlag === "isLogin"){

    //设置vuex登录状态为已登录
    store.state.isLogin = true
    next()

    //如果已登录,还想想进入登录注册界面,则定向回首页
    if (!to.meta.isLogin) {
       //iViewUi友好提示
      iView.Message.error('请先退出登录')
      next({
        path: '/home'
      })
    }
  
  //如果登录标志不存在,即未登录
  }else{

    //用户想进入需要登录的页面,则定向回登录界面
    if(to.meta.isLogin){
      next({
        path: '/login',
      })
      //iViewUi友好提示
      iView.Message.info('请先登录')
    //用户进入无需登录的界面,则跳转继续
    }else{
      next()
    }

  }

});

router.afterEach(route => {
  window.scroll(0, 0);
});

这样就已经完成了Vue的登录注册,当用户关闭浏览器或者第二天再次进入网站,用户依旧可以保持着登录的状态直到用户手动退出登录。

Tips:用户退出只需要localStorage.removeItem("Flag")即可

如果有什么疑问欢迎留言,有错误或者有更简单的方法欢迎大力指出~~

查看原文

于谦 提出了问题 · 2019-08-14

vscode的HTML Language Features总是报错如何解决?

每次新建html文件 总是有这种报错 请问大家如何解决谢谢!

clipboard.png

关注 1 回答 0

于谦 回答了问题 · 2019-08-13

解决express调用next();

function a(){
    var a1 = 'Hello World!';
    next(a1);
}

app.get('/b', a, function (a1, req, res) {
  res.send(a1);
});

关注 3 回答 2

于谦 回答了问题 · 2019-08-01

解决webpack打包如何输出整个目录

outputPath: '[path][name].[ext]'

关注 2 回答 2

认证与成就

  • 获得 2 次点赞
  • 获得 22 枚徽章 获得 0 枚金徽章, 获得 2 枚银徽章, 获得 20 枚铜徽章

擅长技能
编辑

(゚∀゚ )
暂时没有

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2018-09-11
个人主页被 245 人浏览