sheng00 的所有文章

RESTful API 设计指南

作者: 阮一峰
网络应用程序,分为前端和后端两个部分。当前的发展趋势,就是前端设备层出不穷(手机、平板、桌面电脑、其他专用设备……)。
因此,必须有一种统一的机制,方便不同的前端设备与后端进行通信。这导致API构架的流行,甚至出现”API First”的设计思想。RESTful API是目前比较成熟的一套互联网应用程序的API设计理论。我以前写过一篇《理解RESTful架构》,探讨如何理解这个概念。
今天,我将介绍RESTful API的设计细节,探讨如何设计一套合理、好用的API。我的主要参考资料是这篇《Principles of good RESTful API Design》。
RESTful API
一、协议
API与用户的通信协议,总是使用HTTPs协议。
二、域名
应该尽量将API部署在专用域名之下。

https://api.example.com

如果确定API很简单,不会有进一步扩展,可以考虑放在主域名下。

https://example.org/api/

三、版本(Versioning)
应该将API的版本号放入URL。

https://api.example.com/v1/

另一种做法是,将版本号放在HTTP头信息中,但不如放入URL方便和直观。
四、路径(Endpoint)
路径又称”终点”(endpoint),表示API的具体网址。
在RESTful架构中,每个网址代表一种资源(resource),所以网址中不能有动词,只能有名词,而且所用的名词往往与数据库的表格名对应。一般来说,数据库中的表都是同种记录的”集合”(collection),所以API中的名词也应该使用复数。
举例来说,有一个API提供动物园(zoo)的信息,还包括各种动物和雇员的信息,则它的路径应该设计成下面这样。

https://api.example.com/v1/zoos
https://api.example.com/v1/animals
https://api.example.com/v1/employees

五、HTTP动词
对于资源的具体操作类型,由HTTP动词表示。
常用的HTTP动词有下面五个(括号里是对应的SQL命令)。

GET(SELECT):从服务器取出资源(一项或多项)。
POST(CREATE):在服务器新建一个资源。
PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)。
PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性)。
DELETE(DELETE):从服务器删除资源。

还有两个不常用的HTTP动词。
HEAD:获取资源的元数据。
OPTIONS:获取信息,关于资源的哪些属性是客户端可以改变的。
下面是一些例子。

GET /zoos:列出所有动物园
POST /zoos:新建一个动物园
GET /zoos/ID:获取某个指定动物园的信息
PUT /zoos/ID:更新某个指定动物园的信息(提供该动物园的全部信息)
PATCH /zoos/ID:更新某个指定动物园的信息(提供该动物园的部分信息)
DELETE /zoos/ID:删除某个动物园
GET /zoos/ID/animals:列出某个指定动物园的所有动物
DELETE /zoos/ID/animals/ID:删除某个指定动物园的指定动物

六、过滤信息(Filtering)
如果记录数量很多,服务器不可能都将它们返回给用户。API应该提供参数,过滤返回结果。
下面是一些常见的参数。

?limit=10:指定返回记录的数量
?offset=10:指定返回记录的开始位置。
?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序。
?animal_type_id=1:指定筛选条件

参数的设计允许存在冗余,即允许API路径和URL参数偶尔有重复。比如,GET /zoo/ID/animals 与 GET /animals?zoo_id=ID 的含义是相同的。
七、状态码(Status Codes)
服务器向用户返回的状态码和提示信息,常见的有以下一些(方括号中是该状态码对应的HTTP动词)。

200 OK - [GET]:服务器成功返回用户请求的数据,该操作是幂等的(Idempotent)。
201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。
204 NO CONTENT - [DELETE]:用户删除数据成功。
400 INVALID REQUEST - [POST/PUT/PATCH]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作,该操作是幂等的。。
404 NOT FOUND - [*]:用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。
500 INTERNAL SERVER ERROR - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。

状态码的完全列表参见这里。
八、错误处理(Error handling)
如果状态码是4xx,就应该向用户返回出错信息。一般来说,返回的信息中将error作为键名,出错信息作为键值即可。

{
error: "Invalid API key"
}

九、返回结果
针对不同操作,服务器向用户返回的结果应该符合以下规范。

GET /collection:返回资源对象的列表(数组)
GET /collection/resource:返回单个资源对象
POST /collection:返回新生成的资源对象
PUT /collection/resource:返回完整的资源对象
PATCH /collection/resource:返回完整的资源对象
DELETE /collection/resource:返回一个空文档

十、Hypermedia API
RESTful API最好做到Hypermedia,即返回结果中提供链接,连向其他API方法,使得用户不查文档,也知道下一步应该做什么。
比如,当用户向api.example.com的根目录发出请求,会得到这样一个文档。

{"link": {
"rel": "collection https://www.example.com/zoos",
"href": "https://api.example.com/zoos",
"title": "List of zoos",
"type": "application/vnd.yourformat+json"
}}

上面代码表示,文档中有一个link属性,用户读取这个属性就知道下一步该调用什么API了。rel表示这个API与当前网址的关系(collection关系,并给出该collection的网址),href表示API的路径,title表示API的标题,type表示返回类型。
Hypermedia API的设计被称为HATEOAS。Github的API就是这种设计,访问api.github.com会得到一个所有可用API的网址列表。

{
"current_user_url": "https://api.github.com/user",
"authorizations_url": "https://api.github.com/authorizations",
// ...
}

从上面可以看到,如果想获取当前用户的信息,应该去访问api.github.com/user,然后就得到了下面结果。

{
"message": "Requires authentication",
"documentation_url": "https://developer.github.com/v3"
}

上面代码表示,服务器给出了提示信息,以及文档的网址。
十一、其他
(1)API的身份认证应该使用OAuth 2.0框架。
(2)服务器返回的数据格式,应该尽量使用JSON,避免使用XML。

Ionic and Cordova's DeviceReady – My Solution

Folks know that I’ve been madly in love with the?Ionic framework?lately, but I’ve run into an issue that I’m having difficulty with. I thought I’d blog about the problem and demonstrate a solution that worked for me. To be clear, I think my solution is probably?wrong. It works, but it just doesn’t?feel?right. I’m specifically sharing this blog entry as a way to start the discussion and get some feedback. On the slim chance that what I’m showing?is?the best solution… um… yes… I planned that. I’m brilliant.

The Problem

So let’s begin by discussing the problem. Given a typical Ionic app, your Angular code will have a .run method that listens for the ionicPlatform’s ready event. Here is an example from the “base” starter app (https://github.com/driftyco/ionic-app-base/blob/master/www/js/app.js):
 

// Ionic Starter App
// angular.module is a global place for creating, registering and retrieving Angular modules
// 'starter' is the name of this angular module example (also set in a attribute in index.html)
// the 2nd parameter is an array of 'requires'
angular.module('starter', ['ionic'])
.run(function($ionicPlatform) {
  $ionicPlatform.ready(function() {
    // Hide the accessory bar by default (remove this to show the accessory bar above the keyboard
    // for form inputs)
    if(window.cordova && window.cordova.plugins.Keyboard) {
      cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
    }
    if(window.StatusBar) {
      // Set the statusbar to use the default style, tweak this to
      // remove the status bar on iOS or change it to use white instead of dark colors.
      StatusBar.styleDefault();
    }
  });
})

The ionicPlatform.ready event is called when Cordova’s deviceready event fires. When run on a desktop, it is fired when on window.load. Ok, so in my mind, this is where I’d put code that’s normally in a document.ready block. So far so good.
Now let’s imagine you want to use a plugin, perhaps the Device plugin. Imagine you want to simply copy a value to $scope so you can display it in a view. If that controller/view is the first view in your application, you end up with a race condition. Angular is going to display your view and fire off ionicPlatform.ready asynchronously. That isn’t a bug of course, but it raises the question. If you want to make use of Cordova plugin features, and your application depends on it immediately, how do you handle that easily?
One way would be to remove ng-app from the DOM and bootstrap Angular yourself. I’ve done that… once before and I see how it makes sense. But I didn’t want to use that solution this time as I wanted to keep using ionicPlatform.ready. I assumed (and I could be wrong!) that I couldn’t keep that and remove the ng-app bootstraping.
So what I did was to add an intermediate view to my application. A simple landing page. I modified the stateProvider to add a new state and then made it the default. In my ionicPlatform.ready, I use the location service to do a move to the previously default state.

.run(function($ionicPlatform,$location,$rootScope) {
  $ionicPlatform.ready(function() {
    // Hide the accessory bar by default (remove this to show the accessory bar above the keyboard
    // for form inputs)
    if(window.cordova && window.cordova.plugins.Keyboard) {
      cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
    }
    if(window.StatusBar) {
      // org.apache.cordova.statusbar required
      StatusBar.styleDefault();
    }
	  $location.path('/tab/dash');
	  $rootScope.$apply();
  });
})

This seemed to do the trick. My controller code that’s run for the views after this was able to use Cordova plugins just fine. How about a real example?

The Demo

One of the more recent features to land in Ionic is?striped-style tabs. This is an Android-style tab UI and it will be applied automatically to apps running on Android. The difference is a bit subtle when the tabs are on the bottom:

But when moved to the top using tabs-top, it is a bit more dramatic.

Ok… cool. But I wondered – how can I get tabs on top?just?for Android? While I’m not one of those people who believe that UI elements have to be in a certain position on iOS versus Android, I was curious as to how I’d handle this programmatically.
Knowing that it was trivial to check the Device plugin, and having a way now to delay the view until my plugins were loaded, I decided to use the approach described above to ensure I could access the platform before that particular view loaded.
Here is the app.js file I used, modified from the tabs starter template.

// Ionic Starter App
// angular.module is a global place for creating, registering and retrieving Angular modules
// 'starter' is the name of this angular module example (also set in a attribute in index.html)
// the 2nd parameter is an array of 'requires'
// 'starter.services' is found in services.js
// 'starter.controllers' is found in controllers.js
angular.module('starter', ['ionic', 'starter.controllers', 'starter.services'])
.run(function($ionicPlatform,$location,$rootScope) {
  $ionicPlatform.ready(function() {
    // Hide the accessory bar by default (remove this to show the accessory bar above the keyboard
    // for form inputs)
    if(window.cordova && window.cordova.plugins.Keyboard) {
      cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
    }
    if(window.StatusBar) {
      // org.apache.cordova.statusbar required
      StatusBar.styleDefault();
    }
	  $location.path('/tab/dash');
	  $rootScope.$apply();
  });
})
.config(function($stateProvider, $urlRouterProvider) {
  // Ionic uses AngularUI Router which uses the concept of states
  // Learn more here: https://github.com/angular-ui/ui-router
  // Set up the various states which the app can be in.
  // Each state's controller can be found in controllers.js
  $stateProvider
    // setup an abstract state for the tabs directive
  	.state('home', {
		url:"/home",
		templateUrl:'templates/loading.html',
		controller:'HomeCtrl'
	})
    .state('tab', {
      url: "/tab",
      abstract: true,
      templateUrl: function() {
		  if(window.device.platform.toLowerCase().indexOf("android") >= 0) {
			  return "templates/tabs_android.html";
		  } else {
			  return "templates/tabs.html";
		  }
	  },
    })
    // Each tab has its own nav history stack:
    .state('tab.dash', {
      url: '/dash',
      views: {
        'tab-dash': {
          templateUrl: 'templates/tab-dash.html',
          controller: 'DashCtrl'
        }
      }
    })
    .state('tab.friends', {
      url: '/friends',
      views: {
        'tab-friends': {
          templateUrl: 'templates/tab-friends.html',
          controller: 'FriendsCtrl'
        }
      }
    })
    .state('tab.friend-detail', {
      url: '/friend/:friendId',
      views: {
        'tab-friends': {
          templateUrl: 'templates/friend-detail.html',
          controller: 'FriendDetailCtrl'
        }
      }
    })
    .state('tab.account', {
      url: '/account',
      views: {
        'tab-account': {
          templateUrl: 'templates/tab-account.html',
          controller: 'AccountCtrl'
        }
      }
    });
  // if none of the above states are matched, use this as the fallback
  $urlRouterProvider.otherwise('/home');
});

You can see where I use the location.path mechanism after the ionicPlatform.ready event has fired. You can also see where I sniff the device platform to determine which template to run. tabs_android.html is the exact same as tabs.html – but with the tabs-top class applied (*). The biggest drawback here is that the application would error when run on the desktop. That could be avoided by sniffing for the lack of window.device and just setting it to some default: window.device = {platform : "ios"};
So that’s it. What do you think? I have to imagine there is a nicer way of handling this. Maybe I’m being lazy but I want to use Ionic’s killer directives and UX stuff along with Cordova plugins and not have to use an awkward workaround like this.
* A quick footnote. I noticed that if I tried to add tabs-top to the ion-tabs directive, it never worked. For example, this is what I tried first:<ion-tabs ng-class="{'tabs-top':settings.isAndroid}"> I used code in my controller that always set it to true (I wasn’t worried about the device plugin yet) and it never actually updated the view. It’s like the controller scope couldn’t modify the view for some odd reason.

Ionic可折叠列表

html:

<html ng-app="ionicApp">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
    <title>Ionic collapsible list</title>
    <link href="//code.ionicframework.com/1.0.0-beta.12/css/ionic.css" rel="stylesheet">
    <script src="//code.ionicframework.com/1.0.0-beta.12/js/ionic.bundle.js"></script>
    </script>
  </head>
  <body ng-controller="MyCtrl">
    <ion-header-bar class="bar-positive">
      <h1 class="title">Ionic collapsible list</h1>
    </ion-header-bar>
    <ion-content>
      <ion-list>
        <div ng-repeat="group in groups">
          <ion-item class="item-stable"
                    ng-click="toggleGroup(group)"
                    ng-class="{active: isGroupShown(group)}">
              <i class="icon" ng-class="isGroupShown(group) ? 'ion-minus' : 'ion-plus'"></i>
            &nbsp;
            Group {{group.name}}
          </ion-item>
          <ion-item class="item-accordion"
                    ng-repeat="item in group.items"
                    ng-show="isGroupShown(group)">
            {{item}}
          </ion-item>
        </div>
      </ion-list>
    </ion-content>
  </body>
</html>

css

.list .item.item-accordion {
  line-height: 38px;
  padding-top: 0;
  padding-bottom: 0;
  transition: 0.09s all linear;
}
.list .item.item-accordion.ng-hide {
  line-height: 0px;
}
.list .item.item-accordion.ng-hide-add,
.list .item.item-accordion.ng-hide-remove {
  display: block !important;
}

js

angular.module('ionicApp', ['ionic'])
.controller('MyCtrl', function($scope) {
  $scope.groups = [];
  for (var i=0; i<10; i++) {
    $scope.groups[i] = {
      name: i,
      items: [],
      show: false
    };
    for (var j=0; j<3; j++) {
      $scope.groups[i].items.push(i + '-' + j);
    }
  }
  /*
   * if given group is the selected group, deselect it
   * else, select the given group
   */
  $scope.toggleGroup = function(group) {
    group.show = !group.show;
  };
  $scope.isGroupShown = function(group) {
    return group.show;
  };
});

codepen例子:

See the Pen Ionic collapsible list by Qing Sheng (@shengoo) on CodePen.

jsfiddle:

Add jsonp support for your .net mvc api project

使用Attribute来实现jsonp支持

public class JsonpAttribute : ActionFilterAttribute
{
  private const string CallbackQueryParameter = "callback";
  public override void OnActionExecuted(HttpActionExecutedContext context)
  {
    var callback = string.Empty;
    if (IsJsonp(out callback))
    {
      var jsonBuilder = new StringBuilder(callback);
      jsonBuilder.AppendFormat("({0})", context.Response.Content.ReadAsStringAsync().Result);
      context.Response.Content = new StringContent(jsonBuilder.ToString());
    }
    base.OnActionExecuted(context);
  }
  private bool IsJsonp(out string callback)
  {
    callback = HttpContext.Current.Request.QueryString[CallbackQueryParameter];
    return !string.IsNullOrEmpty(callback);
  }
}

然后在你的WebApiConfig里面加上
config.Filters.Add(new JsonpAttribute());
这样你的api既能支持json,又能支持jsonp了。
如果url里有?callback=?,就会返回jsonp格式的数据,如果没有,依然是json

angularjs星级评分 star rating directive

directive

var app = angular.module("app", []);
app.controller('ctrl',function($scope){
  $scope.max = 5;
  $scope.ratingVal = 2;
  $scope.readonly = false;
  $scope.onHover = function(val){
    $scope.hoverVal = val;
  };
  $scope.onLeave = function(){
    $scope.hoverVal = null;
  }
  $scope.onChange = function(val){
    $scope.ratingVal = val;
  }
});
app.directive('star', function () {
  return {
    template: '<ul class="rating" ng-mouseleave="leave()">' +
        '<li ng-repeat="star in stars" ng-class="star" ng-click="click($index + 1)" ng-mouseover="over($index + 1)">' +
        '\u2605' +
        '</li>' +
        '</ul>',
    scope: {
      ratingValue: '=',
      max: '=',
      readonly: '@',
      onHover: '=',
      onLeave: '='
    },
    controller: function($scope){
      $scope.ratingValue = $scope.ratingValue || 0;
      $scope.max = $scope.max || 5;
      $scope.click = function(val){
        if ($scope.readonly && $scope.readonly === 'true') {
          return;
        }
        $scope.ratingValue = val;
      };
      $scope.over = function(val){
        $scope.onHover(val);
      };
      $scope.leave = function(){
        $scope.onLeave();
      }
    },
    link: function (scope, elem, attrs) {
      elem.css("text-align", "center");
      var updateStars = function () {
        scope.stars = [];
        for (var i = 0; i < scope.max; i++) {
          scope.stars.push({
            filled: i < scope.ratingValue
          });
        }
      };
      updateStars();
      scope.$watch('ratingValue', function (oldVal, newVal) {
        if (newVal) {
          updateStars();
        }
      });
      scope.$watch('max', function (oldVal, newVal) {
        if (newVal) {
          updateStars();
        }
      });
    }
  };
});

css:

.rating {
  color: #a9a9a9;
  margin: 0;
  padding: 0;
}
ul.rating {
  display: inline-block;
}
.rating li {
  list-style-type: none;
  display: inline-block;
  padding: 1px;
  text-align: center;
  font-weight: bold;
  cursor: pointer;
}
.rating .filled {
  color: #ffee33;
}

html:

<div ng-app="app">
<div class="well" ng-controller="ctrl">
  <div star rating-value="ratingVal" max="max" on-hover="onHover" on-leave="onLeave" readonly="{{readonly}}"></div>
  <div>
    Max: <input type="number" name="input" ng-model="max" min="0" max="99" required>
    <p>current value: {{ratingVal}}</p>
    <p>hover on : {{hoverVal?hoverVal:"none"}}</p>
    <p>readonly : <input type="checkbox"
       ng-model="readonly"></p>
  </div>
</div>
</div>

github page

http://shengoo.github.io/angularjs-practice/directive/star/star.html

jsfiddle:

code pen:

See the Pen angularjs star rating directive by Qing Sheng (@shengoo) on CodePen.

如何面试前端工程师: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