分类目录归档:JavaScript

前端使用浏览器实现asr语音识别


// 创建一个新的SpeechRecognition对象 var recognition = new (window.SpeechRecognition || window.webkitSpeechRecognition || window.mozSpeechRecognition || window.msSpeechRecognition)(); recognition.lang = 'en-US'; // 设置语言 recognition.interimResults = false; // 设置是否返回临时结果 recognition.maxAlternatives = 1; // 设置返回的最大替代词数量 // 当识别到语音结束时,返回结果 recognition.onresult = function(event) { console.log('You said: ', event.results[0][0].transcript); }; // 开始语音识别 recognition.start();

demo

 

fetch 用法简介

Fetch API提供了一个获取资源的接口(包括跨域)。任何使用过 XMLHttpRequest 的人都能轻松上手,但新的API提供了更强大和灵活的功能集。

参数

第一个参数是URL地址。

第二个参数(可选)是fetch使用的选项(method、headers等选项)

返回结果

fetch会返回一个promise对象,resolve对应请求的Response。

response属性:

  1. body
  2. bodyUsed

    一个布尔值来标示该Response是否读取过Body

  3. headers

    此Response所关联的Headers 对象.

  4. ok

    一个布尔值来标示该Response成功(状态码200-299) 还是失败.

  5. redirected

    该Response是否来自一个重定向,如果是的话,它的URL列表将会有多个

  6. status

    Response的状态码 (例如, 200 成功).

  7. statusText

    与该Response状态码一致的状态信息 (例如, OK对应200).

  8. type

    Response的类型 (例如, basic, cors).

  9. url

    Response的URL.

response方法

  1. arrayBuffer()

    读取 Response对象并且将它设置为已读(因为Responses对象被设置为了 stream 的方式,所以它们只能被读取一次) ,并返回一个被解析为ArrayBuffer格式的promise对象

  2. blob()

    读取 Response对象并且将它设置为已读(因为Responses对象被设置为了 stream 的方式,所以它们只能被读取一次) ,并返回一个被解析为Blob格式的promise对象

  3. formData()

    读取Response对象并且将它设置为已读(因为Responses对象被设置为了 stream 的方式,所以它们只能被读取一次) ,并返回一个被解析为FormData格式的promise对象

  4. json()

    读取 Response对象并且将它设置为已读(因为Responses对象被设置为了 stream 的方式,所以它们只能被读取一次) ,并返回一个被解析为JSON格式的promise对象

  5. text()

    读取 Response对象并且将它设置为已读(因为Responses对象被设置为了 stream 的方式,所以它们只能被读取一次) ,并返回一个被解析为USVString格式的promise对象

示例:读取一个图片并生成URL

var myImage = document.querySelector('.my-image');
fetch('flowers.jpg').then(function(response) {
  return response.blob();
}).then(function(response) {
  var objectURL = URL.createObjectURL(response);
  myImage.src = objectURL;
});

get请求

fetch('http://example.com/movies.json')
  .then(function(response) {
    return response.json();
  })
  .then(function(myJson) {
    console.log(myJson);
  });

post请求

fetch(url, {
  method: 'POST',
  body: JSON.stringify(data),
  headers: new Headers({
    'Content-Type': 'application/json'
  })
})

上传文件&提交表单

var formData = new FormData();
var fileField = document.querySelector("input[type='file']");

formData.append('username', 'abc123');
formData.append('avatar', fileField.files[0]);

fetch('https://example.com/upload', {
  method: 'PUT',
  body: formData
})
.then(response => response.json())
.catch(error => console.error('Error:', error))
.then(response => console.log('Success:', response));

移动端禁用和启用滚动、拖动

弹窗和弹框是网站应用中经常用到的技术,但是在页面弹出一个全屏的弹窗的时候,还是可以使用触摸来滑动弹窗下面的页面。

为了用户有更好的体验,可以使用以下代码,在弹窗的时候禁用滚动,弹窗关闭的时候再次启用滚动。

// left: 37, up: 38, right: 39, down: 40,
// spacebar: 32, pageup: 33, pagedown: 34, end: 35, home: 36
var keys = {37: 1, 38: 1, 39: 1, 40: 1};

function preventDefault(e) {
    e = e || window.event;
    if (e.preventDefault)
        e.preventDefault();
    e.returnValue = false;
}

function preventDefaultForScrollKeys(e) {
    if (keys[e.keyCode]) {
        preventDefault(e);
        return false;
    }
}

function disableScroll() {
    if (window.addEventListener) // older FF
        window.addEventListener('DOMMouseScroll', preventDefault, false);
    window.onwheel = preventDefault; // modern standard
    window.onmousewheel = document.onmousewheel = preventDefault; // older browsers, IE
    window.ontouchmove  = preventDefault; // mobile
    document.onkeydown  = preventDefaultForScrollKeys;
}

function enableScroll() {
    if (window.removeEventListener)
        window.removeEventListener('DOMMouseScroll', preventDefault, false);
    window.onmousewheel = document.onmousewheel = null;
    window.onwheel = null;
    window.ontouchmove = null;
    document.onkeydown = null;
}

使用ES6的Proxy实现简单的双向绑定

ES6的Proxy对象可以用来拦截一个Object对象的属性的修改,这次我们利用它来实现一个简单的双向绑定。
废话不多说,请看代码。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>View to model</title>
</head>
<body>
    <div>
        <!-- 使用'bind-to'的属性来标记这个输入框里的值会对Model里的属性进行修改 -->
        <input bind-to="name" />
        <input bind-to="name" />
    </div>
    <!-- 使用#属性名#来把Model里的值映射到DOM中 -->
    <span>#name#</span>
    <span>#name#</span>
    <button onclick="reset()">reset</button>
    <script type="text/javascript">
        var handler = {
            // 拦截属性的设置,触发试图的更新
            set: function(target, key, value, receiver) {
                target[key] = value;
                updateView(propertyName);
                return Reflect.set(target, key, value);
            },
        };
        // 双向绑定的对象
        var model = new Proxy({}, handler);
        // 双向绑定列表
        var inputs = {},views = {};
        var all = document.all;
        // 遍历所有DOM元素
        for(var i=0,l=all.length;i<l;i++){
            // 找到所有有bind-to的输入框
            if(all[i].getAttribute('bind-to')){
                var dom = all[i];
                var propertyName = dom.getAttribute('bind-to');
                inputs[propertyName] = inputs[propertyName] ? inputs[propertyName] : [];
                views[propertyName] = views[propertyName] ? views[propertyName] : [];
                inputs[propertyName].push(dom);
                dom.addEventListener('change',function function_name(e) {
                    var propertyName = e.target.getAttribute('bind-to');
                    model[propertyName] = e.target.value;
                })
            }
            // 找到所有model驱动的页面元素
            if(all[i].innerHTML && all[i].innerHTML.length>2 &&
                all[i].innerHTML[0] == '#' &&
                all[i].innerHTML[all[i].innerHTML.length-1] == '#'){
                var propertyName = all[i].innerHTML.slice(1,all[i].innerHTML.length-1);
                inputs[propertyName] = inputs[propertyName] ? inputs[propertyName] : [];
                views[propertyName] = views[propertyName] ? views[propertyName] : [];
                views[propertyName].push(all[i]);
            }
        }
        // 更新View
        function updateView(propertyName) {console.log('update ' + propertyName);
            if(views[propertyName]){
                for(var i = 0,l = views[propertyName].length;i < l;i++){
                    views[propertyName][i].innerText = model[propertyName];
                }
            }
            if(inputs[propertyName]){
                for(var i = 0,l = inputs[propertyName].length;i < l;i++){
                    inputs[propertyName][i].value = model[propertyName];
                }
            }
        }
        function reset() {
            for(var name in model){
                model[name] = '';
            }
        }
    </script>
</body>
</html>

让很多Promise一个接一个进行

Promise是我们处理异步操作的时候经常用到的。
有的时候我们会需要顺序执行很多异步操作,如果操作比较少的时候还可以写成a.then(b).then(c)...,可是如果异步操作很多的时候就不能这样写了。
可以利用数组的reduce函数达到同样的效果,这个时候不管有多少异步操作都可以连在一起了。
代码如下:

function waitPromise(time) {
    console.log(time);
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('resolved');
        }, time);
    });
}
function log(data) {
    return new Promise((resolve, reject) => {
        console.log(data + '@' + (new Date().getSeconds()));
        resolve();
    });
}
var ps = [];
for (var i = 0; i < 3; i++) {
    let time = (i + 1) * 1000;
    ps.push(() => waitPromise(time));
    ps.push(log);
}
console.log('started' + '@' + (new Date().getSeconds()));
var p = Promise.resolve();
ps.reduce((p, c) => {
    return p.then(c)
}, p).then(() => {
    console.log('all finished');
}).catch(reject => {
    console.log('reject', reject)
});

输出结果:

started@59
1000
resolved@0
2000
resolved@2
3000
resolved@5
all finished

 

如何面试前端工程师:Github很重要

这篇文章从面试官的角度介绍到面试时可能会问到的一些问题。
我在Twitter和Stripe的一部分工作内容是面试前端工程师。其实关于面试你可能很有自己的一套,这里我想跟你们分享一下我常用的方法。
不过我想先给你们一个忠告,招聘是一件非常艰巨的任务,在45分钟内指出一名侯选人是否合适是你需要完成的任务。不过面试的最大问题是每个人都会想着去雇佣他们自己,任何通过我面试的人想法大都跟我差不多(注:因为你总会问你自己关心和知道的问题),这其实不是一件好事。因此我之前的决定都有很大碰运气的成分。不过,这也是一个良好的开端。
最理想的情况下是侯选人有一个全面的Github“简历”,这样我们可以同时通过他们的开源项目了解他们。我经常会浏览他们的代码然后针对一些特定的代码设计问一些问题。如果侯选人有非常好的开源项目记录,接下来的面试会直接去检验他们的团队协作精神。否则,我不得不去问他们一些代码方面的问题了。
我的面试非常有实践性,全部是写代码。没有抽象和理论上的东西(注:作者是个行业派),其他的面试官很可能会问这些问题,但是我认为他们前端编程的能力是值得商榷的。我问的问题大多看上去非常简单,但是每组问题都能让我考查侯选人某一方面JavaScript的知识。
第一部分:Object Prototypes (对象原型)
刚开始很简单。我会让侯选人去定义一个方法,传入一个string类型的参数,然后将string的每个字符间加个空格返回,例如:

  spacify('hello world') // => 'h e l l o  w o r l d'

尽管这个问题似乎非常简单,其实这是一个很好的开始,尤其是对于那些未经过电话面试的侯选人——他们很多人声称精通JavaScript,但通常连一个简单的方法都不会写。
下面是正确答案,有时侯选人可能会用一个循环,这也是一种可接受的答案。

   function spacify(str) {
      return str.split('').join(' ');
    }

接下来,我会问侯选人,如何把这个方法放入String对象上面,例如:

 'hello world'.spacify();

问这个问题可以让我考察侯选人是否对function prototypes(方法原型)有一个基本的理解。这个问题会经常引起一些有意思的讨论:直接在对象的原型(prototypes)上添加方法是否安全,尤其是在Object对象上。最后的答案可能会像这样:

String.prototype.spacify = function(){
      return this.split('').join(' ');
    };

到这儿,我通常会让侯选人解释一下函数声明和函数表达式的区别。
第二部分:参数 arguments
下一步我会问一些简单的问题去考察侯选人是否理解参数(arguments)对象。我会让他们定义一个未定义的log方法作为开始。

 log('hello world')

我会让侯选人去定义log,然后它可以代理console.log的方法。正确的答案是下面几行代码,其实更好的侯选人会直接使用apply.

  function log(msg) {
      console.log(msg);
    }

他们一旦写好了,我就会说我要改变我调用log的方式,传入多个参数。我会强调我传入参数的个数是不定的,可不止两个。这里我举了一个传两个参数的例子。

 log('hello', 'world');

希望你的侯选人可以直接使用apply。有时人他们可能会把apply和call搞混了,不过你可以提醒他们让他们微调一下。传入console的上下文也非常重要。

 function log(){
      console.log.apply(console, arguments);
    };

接下来我会让侯选人给每一个log消息添加一个”(app)”的前辍,比如:

   '(app) hello world'

现在可能有点麻烦了。好的侯选人知道arugments是一个伪数组,然后会将他转化成为标准数组。通常方法是使用Array.prototype.slice,像这样:

 function log(){
      var args = Array.prototype.slice.call(arguments);
      args.unshift('(app)');
      console.log.apply(console, args);
    };

第三部分:上下文
下一组问题是考察侯选人对上下文和this的理解。我先定义了下面一个例子。注意count属性不是只读取当前下下文的。

var User = {
  count: 1,
  getCount: function() {
    return this.count;
  }
};

我又写了下面几行,然后问侯选人log输出的会是什么。

console.log(User.getCount());
    var func = User.getCount;
    console.log(func());

这种情况下,正确的答案是1和undefined。你会很吃惊,因为有很多人被这种最基础的上下文问题绊倒。func是在winodw的上下文中被执行的,所以会访问不到count属性。我向侯选人解释了这点,然后问他们怎么样保证User总是能访问到func的上下文,即返回正即的值:1
正确的答案是使用Function.prototype.bind,例如:

   var func = User.getCount.bind(User);
    console.log(func());

接下来我通常会说这个方法对老版本的浏览器不起作用,然后让侯选人去解决这个问题。很多弱一些的侯选人在这个问题上犯难了,但是对于你来说雇佣一个理解apply和call的侯选人非常重要。

 Function.prototype.bind = Function.prototype.bind || function(context){
      var self = this;
      return function(){
        return self.apply(context, arguments);
      };
    }

第四部分:弹出窗口(Overlay library)
面试的最后一部分,我会让侯选人做一些实践,通过做一个‘弹出窗口’的库。我发现这个非常有用,它可以全面地展示一名前端工程师的技能:HTML,CSS和JavaScript。如果侯选人通过了前面的面试,我会马上让他们回答这个问题。
实施方案是由侯选人自己决定的,但是我也希望他们能通过以下几点来实现:
在遮罩中最好使用position中的fixed代替absolute属性,这样即使在滚动的时侯,也能始终让遮罩始盖住整个窗口。当侯选人忽略时我会提示他们这一点,并让他们解释fixed和absolute定位的区别。

   .overlay {
      position: fixed;
      left: 0;
      right: 0;
      bottom: 0;
      top: 0;
      background: rgba(0,0,0,.8);
    }

他们如何让里面的内容居中也是需要考察的一点。一些侯选人会选择CSS和绝对定位,如果内容有固定的宽、高这是可行的。否则就要使用JavaScript.

  .overlay article {
      position: absolute;
      left: 50%;
      top: 50%;
      margin: -200px 0 0 -200px;
      width: 400px;
      height: 400px;
    }

我也会让侯选人确保当遮罩被点击时要自动关闭,这会很好地考查事件冒泡机制的机会。通常侯选人会在overlay上面直接绑定一个点击关闭的方法。

  $('.overlay').click(closeOverlay);

这是个方法,不过直到你认识到点击窗口里面的东西也会关闭overlay的时侯——这明显是个BUG。解决方法是检查事件的触发对象和绑定对象是否一致,从而确定事件不是从子元素里面冒上来的,就像这样:

   $('.overlay').click(function(e){
      if (e.target == e.currentTarget)
        closeOverlay();
    });

其他方面
当然这些问题只能覆盖前端一点点的知识的,还有很多其他的方面你有可能会问到,像性能,HTML5 API, AMD和CommonJS模块模型,构造函数(constructors),类型和盒子模型(box model)。根据侯选人的情况,我经常会随机提些问题。
来自:
链接:http://ourjs.com/detail/52c4145d7986593603000009

JavaScript:工厂模式

工厂模式定义:实例化对象,用工厂方法代替new操作。

function CarMaker () {
}
CarMaker.prototype.drive = function () {
  console.log("Vroom, I have " + this.doors + " doors");
}
CarMaker.factory = function (type) {
  var constr = type,
  newcar;
  if(typeof CarMaker[constr] !== "function"){
    throw{
      name:"Error",
      message: constr + " doesn't exist"
    };
  }
  if(typeof CarMaker[constr].prototype.drive !=="function"){
    CarMaker[constr].prototype = new CarMaker();
  }
  newcar = new CarMaker[constr]();
  return newcar;
}
CarMaker.Compact = function(){
  this.doors = 4;
}
CarMaker.Convertible = function(){
  this.doors = 2;
}
CarMaker.SUV = function(){
  this.doors = 3;
}
var corolla = CarMaker.factory('Compact');
var solstice = CarMaker.factory('Convertible');
var cherokee = CarMaker.factory('SUV');
corolla.drive();
solstice.drive();
cherokee.drive();

内置的Object也表现了工厂的行为:

var o = new Object(),
    n = new Object(1),
    s = Object("s"),
    b = Object(true);
console.log(o.constructor);
console.log(n.constructor);
console.log(s.constructor);
console.log(b.constructor);