Coder Social home page Coder Social logo

fragment's People

Contributors

geekaholiclin avatar

Stargazers

 avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

fragment's Issues

Gitの代码回滚

今天在搭建react脚手架的时候,因为测试npm scripts 的git 钩子,不断地连续几次提交进行测试,当写好之后想提交的时候发觉没有必要添加非必要的commit到分支里面,污染历史记录。

所以查找如何解决这个的问题同时(最终是使用了git reset 到某个hash指定的commit),想更加深入地了解Git中的代码回滚。


参考地址:
5.2 代码回滚:Reset、Checkout、Revert的选择

使用momentjs遇到的坑

在写后台系统的时候因为需要大量处理时间,引入了momentjs,在antd组件的基础上封装“关于时间段的基础组件“的时候,返回了 moment 对象的引用,随后在上层的调用中,可对其修改和判断。

问题来了,在上层中对返回的 moment 对象业务逻辑处理的时候,总是结果与预期有所出入。结果,在调试的过程中发现,多处对同一个对象进行操作(写代码的时候忘记了 moment 是一个对象),使得moment总是被更改。大体情况和S.O.中的这个情况一样

var enumerateDaysBetweenDates = function(startDate, endDate) {
    var dates = [];

    startDate = startDate.add(1, 'days');

    while(startDate.format('M/D/YYYY') !== endDate.format('M/D/YYYY')) {
      console.log(startDate.toDate());
      dates.push(startDate.toDate());
      startDate = startDate.add(1, 'days');
    }

    return dates;
  };
enumerateDaysBetweenDates( moment(new Date('1/1/2014')), moment(new Date('1/5/2014'));
//Thu Jan 02 2014 00:00:00 GMT-0800 (PST)
//Fri Jan 03 2014 00:00:00 GMT-0800 (PST)
//Sat Jan 04 2014 00:00:00 GMT-0800 (PST)
//[ Sun Jan 05 2014 00:00:00 GMT-0800 (PST),
 // Sun Jan 05 2014 00:00:00 GMT-0800 (PST),
  //Sun Jan 05 2014 00:00:00 GMT-0800 (PST) ]

也就是push进去数组中的三个 moment 对象其实是相同的引用,导致数组中的三个元素是一样的。

而该问题下的答案也给出了解决办法:

  1. 使用 moment 对象的clone方法,拷贝一个新的实例: startDate = startDate.clone().add(1, 'days');
  2. 调用 moment 方法再次处理 moment 对象,创建一个新的 moment 对象:startDate = moment(startDate).add(1, 'days');
var enumerateDaysBetweenDates = function(startDate, endDate) {
    var dates = [];

    var currDate = moment(startDate).startOf('day');
    var lastDate = moment(endDate).startOf('day');

    while(currDate.add(1, 'days').diff(lastDate) < 0) {
        console.log(currDate.toDate());
        dates.push(currDate.clone().toDate());
    }

    return dates;
};

答案中也说明了,推荐使用moment(),原因是moment()可以处理字符串、moment 对象以及其他momentjs中新建moment对象能处理的所有值,而clone()只适用于startDate是 moment 对象。

String和RegExp字符串搜索的相关方法辨析

目前能想到的与字符串匹配的方法有String.prototype.search()String.prototype.match()RepExp.prototype.test()RepExp.prototype.exec()

String.prototype.search()RepExp.prototype.exec()的用途一样,返回值都为boolean类型,只为了测试是否一个String是否匹配正则表达式。

String.prototype.match()RepExp.prototype.exec()的用途一样,当前者的正则表达式中没有g标志的时候,相当于RepExp.prototype.exec(),返回一个带额外属性(input和index)的数组:

  • [0]:匹配的字符串
  • [1]-[n] :捕获组(也就是括号中的正则)
  • input:输入的字符串
  • index :整个匹配索引(从0开始)

所以,当只需要第一个匹配的字符串的时候,可以使用不带g标志的String.prototype.match()RepExp.prototype.exec()

而当String.prototype.match()带上g标志的时候,适合不需要捕获组的全局匹配,返回所有匹配的值的数组(从[0]到[n])。

而如果需要捕获组且全局匹配(返回值与match()不一样),只能使用RegExp.prototype.exec(),用法如下:

var myRe = /ab*/g;
var str = 'abbcdefabh';
var myArray;
while ((myArray = myRe.exec(str)) !== null) {
  var msg = 'Found ' + myArray[0] + '. ';
  msg += 'Next match starts at ' + myRe.lastIndex;
  console.log(msg);
}

react-router 中 setState(...): Can only update a mounted or mounting component警告的由来与解决

在使用React写后台系统的时候,引入了[email protected]版本,有这么一个需求:两个属性配置页。但是我并不想让他们各自占用一个path,而是通过 params 参数的形式,然后加载不同的组件。

 <Route
              path="metadataManagement(/:tab)"
              component={MetadataManagement}
            />

在MetadataManagement页面,进行逻辑处理:

import React from "react";
import { Tabs } from "antd";

import MetaEvent from "./metaEvent";
import UserProps from "./userProps";
const TabPane = Tabs.TabPane;
const tabsContentObjs = [
  {
    name: "元事件分析",
    key: "metaEvent",
    component: MetaEvent
  },
  {
    name: "用户属性",
    key: "userProps",
    component: UserProps
  }
];

export default props => {
  const hasParams = props.params.tab;
  const selectedTab = props.params.tab || tabsContentObjs[0].key;
  if (!hasParams) {
    props.router.replace("/analyse/metadataManagement/" + selectedTab);
  }
  const handleChange = function(key) {
    props.router.replace("/analyse/metadataManagement/" + key);
  };
  const TabPanes = tabsContentObjs.map(obj =>
    <TabPane tab={obj.name} key={obj.key} style={{ overflow: "hidden" }}>
      <obj.component />
    </TabPane>
  );
  return (
    <Tabs
      animated={{ inkBar: true, tabPane: false }}
      defaultActiveKey={selectedTab}
      onChange={handleChange}
      style={{ padding: "0 16px 16px" }}
    >
      {TabPanes}
    </Tabs>
  );
};

关键的代码就在于props.router.replace("/analyse/metadataManagement/" + selectedTab);,当你匹配到/analyse/metadataManagement/的时候会进行替换,替换成/analyse/metadataManagement/metaEvent,问题就出现了。当替换的时候,你的组件并没有加载完成,尚未卸载,而又重新加载刷新一遍。

解决的办法就是:尽可能地避免这种还未加载完成就进行刷新的情况。面对这种情形,可以在进入路由的时候就进行replace,而不是等到页面挂载的时候才进行替换操作。

<Route
              path="metadataManagement(/:tab)"
              component={MetadataManagement}
              onEnter={(nextState, replace) => {
                !nextState.params.tab &&
                  replace("/analyse/metadataManagement/metaEvent");
              }}
            />

其实忽略warning对于当前版本的react并没有什么问题,但是对于后续的升级react过程中可能会导致迁移失败,所以最好还是解决warning

你不知道的JavaScript闭包

一直以为,JavaScript中的闭包一定是下面的形式:

function foo(){
     var a = 'xxx';
     return function(){
          console.log(a);
    }
}

也就是返回一个拥有内部变量的函数这种形式。但是在看redux源码分析的时候,发觉和自己想象中的不一样。所以,寻找资料解决自己心中的疑惑。

关于闭包的定义,我觉得MDN文档已经简明扼要地概括了。

A closure is the combination of a function and the lexical environment within which that function was declared.

用自己蹩脚的英语来翻译就是:闭包是由函数及其函数定义所在的词法环境组成的。

也就是说,下面的形式也是一个闭包:

function foo(){
     var a = 'xxx';
     function bar(){
          console.log(a);
    }
    //return bar;
}

其中bar函数就是一个闭包,它拥有其定义环境中的某个变量。它较之上面的形式,区别在于,它并未返回bar变量。但是这只是利用闭包的特性,对闭包的运用之一,仅此而已。

而闭包的常见形式在StackOverflow的某个中已经很好地说明。

Two one sentence summaries:
A closure is one way of supporting first-class functions; it is an expression that can reference variables within its scope (when it was first declared), be assigned to a variable, be passed as an argument to a function, or be returned as a function result.
Or, a closure is a stack frame which is allocated when a function starts its execution, and not freed after the function returns (as if a 'stack frame' were allocated on the heap rather than the stack!).

而且举的例子很全面,比如多个返回函数是共享一份词法环境,多次调用返回函数是不共享一份词法环境等等。

//多次调用返回函数不共享词法环境的例子
function newClosure(someNum, someRef) {
    // Local variables that end up within closure
    var num = someNum;
    var anArray = [1,2,3];
    var ref = someRef;
    return function(x) {
        num += x;
        anArray.push(num);
        console.log('num: ' + num +
            '; anArray: ' + anArray.toString() +
            '; ref.someVar: ' + ref.someVar + ';');
      }
}
obj = {someVar: 4};
fn1 = newClosure(4, obj);
fn2 = newClosure(5, obj);
fn1(1); // num: 5; anArray: 1,2,3,5; ref.someVar: 4;
fn2(1); // num: 6; anArray: 1,2,3,6; ref.someVar: 4;
obj.someVar++;
fn1(2); // num: 7; anArray: 1,2,3,5,7; ref.someVar: 5;
fn2(2); // num: 8; anArray: 1,2,3,6,8; ref.someVar: 5;

那么问题来了,再以这种思维去看以前遇到的一些问题,比如点击循环输出一连串数字的经典面试问题:

//多个函数共享单份词法作用域
var obj = {};
obj.handleClick = [];
(function clojure(){
  for(var i = 0;i<3;i++){
    obj.handleClick[i] = function(){console.log('click item '+i);}//匿名函数   |<---定义
    }
}());
obj.handleClick[0];//log "click item 3"
obj.handleClick[1];//log "click item 3"
obj.handleClick[2];//log "click item 3"

onfoucus事件触发的时候,匿名回调函数会调用,而console.log()依旧拿着i的引用,所以,匿名回调函数以及clojure内部的词法环境构成闭包,而即便是遍历了3次,拿得3个不同的匿名函数,但是这些闭包在函数定义的时候已经决定是共享一份词法环境。所以当匿名函数回调的时候,获得的item都是3

为什么会出现这种情况?很多人说这类问题是因为js没有块级作用域,用闭包去解决,但实际上这涉及到了JavaScript的一些机制,比如执行上下文。类似的效果就是函数执行的时候会跑到定义函数的地方(也就是该函数所在的词法环境),进行调用。上面的例子中,obj.handleClick调用的时候,会寻找i,而i在循环结束后就变成了3。

对于这类问题,大牛们给出了两类的解决办法:

  • 多个独立词法环境的闭包
  • ES6中的let

下面让我们改正一下上面的栗子。

首先看第一类解决方法:

//第一类方法的方法一
var obj = {};
obj.handleClick = [];
(function clojure() {
  for (var i = 0; i < 3; i++) {
    obj.handleClick[i] = (function(j) {
      return function() {
        console.log('click item ' + j)
      }//新的匿名函数
    })(i); //匿名立即调用函数
  };
}());

利用函数工厂(立即执行函数)并返回新的函数。之所以外包一层,是因为需要用一个函数将clojure函数内部的词法环境隔开,在匿名立即执行函数新增一层词法环境。而j用于形式参数,i用于实际参数,是为了保存在执行循环时传入的i,以便于在新的匿名函数调用时,正确读取循环的i。

这时候你会发现,其实新的匿名函数用于自己独立的词法环境,并不共享(虽然再上一层的词法环境,即clojure()内部会共享,因为根据词法作用域链向上寻找)

第一类方法还有一种形式产生多个独立词法环境:(和上面大同小异,只不过是把return移除,把obj.handleClick[i]直接赋值)

//第一类方法的方法二
var obj = {};
obj.handleClick = [];
(function clojure() {
  for (var i = 0; i < 3; i++) {
    (function(j) {
      obj.handleClick[i] = function() {
        console.log('click item ' + j)
      }
    })(i); //匿名立即调用函数
  };
}());
obj.handleClick[0]();
obj.handleClick[1]();
obj.handleClick[2]();

第二类解决方法就更加简单了,使用ES6中的语法let

//第二类方法的方法一
var obj = {};
obj.handleClick = [];
(function clojure() {
    for (let i = 0; i < 3; i++) {
      obj.handleClick[i] = function() {
        console.log('click item ' + i)
      }
    }
}());
obj.handleClick[0]();
obj.handleClick[1]();
obj.handleClick[2]();

很多人把let的块级作用域和C类语言中的块级作用域一起类比。但是有没有思索过,为什么块级作用域的let就可以解决这类闭包导致的问题?

那让我们初步看看,let为什么能解决这类问题,我把上面let的那段代码用babel编译过后:

'use strict';

var obj = {};
obj.handleClick = [];
(function clojure() {
  var _loop = function _loop(i) {
    obj.handleClick[i] = function () {
      console.log('click item ' + i);
    };
  };

  for (var i = 0; i < 3; i++) {
    _loop(i);
  }
})();
obj.handleClick[0]();
obj.handleClick[1]();
obj.handleClick[2]();

看得出来,其实它就是第一类方法的方法二的形式,只不过用了变量去存取函数,并且循环调用。

再看看另外一种let的形式,放在这里将,只是为了让大家(和我)看看多一种解决办法的形式。

//第二类方法的方法二
var obj = {};
obj.handleClick = [];
(function clojure() {
    for (var i = 0; i < 3; i++) {
      let j = i;//在这里使用let
      obj.handleClick[j] = function() {
        console.log('click item ' + j)
      }
    }
}());
obj.handleClick[0]();
obj.handleClick[1]();
obj.handleClick[2]();
'use strict';
var obj = {};
obj.handleClick = [];
(function clojure() {
  var _loop = function _loop() {
    var j = i;
    obj.handleClick[j] = function () {
      console.log('click item ' + j);
    };
  };

  for (var i = 0; i < 3; i++) {
    _loop();
  }
})();
obj.handleClick[0]();
obj.handleClick[1]();
obj.handleClick[2]();

可以看到编译过后的两种不同的形式,前者是使用参数暂时保存,后者是使用变量j暂时保存。

之前以为,后者的时候,变量j会一直向上寻找i,从而导致j一直为3。(误)

自己的思考:经过一番思考过后,发觉自己搞混淆了执行和回调。当回调的时候确实需要到定义的环境中寻找变量,但是第二类方法二中,_loop()执行后j的值是拿到了遍历的index,也就是1,2或3。这已经是个确切的事实,固定下来的了。_loop()并不是回调,所以不需要用继续寻找最终的i,自然也就不是'全部都是3'

需要记住的是,在JavaScript中采用词法作用域而非动态作用域,所以作用域链的确定是函数定义的时候就已经决定的了


参考资料:

初学Angular中需要注意的点?

  • model object在angular中指的是$scope对象,该对象的属性可以被视图访问,也可以同控制器进行交互。(angular的黑魔法之一)

  • angular在启动并生成视图时,会将根ng-app元素同$rootScope进行绑定,$rootScope是所有$scope对象的最上层,$rootScope是angular中最接近全局作用域的对象。

  • $scope对象在angular中充当data model,并不负责处理和操作数据,它只是视图和控制器之间的胶水。

  • ng-controller指令可以显式地创建新的$scope对象(也可以说是分割出新的作用域)

  • 应该将复杂的逻辑放到指令和服务中,然后将指令和服务依赖注入到控制器。

In general, a Controller shouldn't try to do too much. It should contain only the business logic needed for a single view.
The most common way to keep Controllers slim is by encapsulating work that doesn't belong to controllers into services and then using these services in Controllers via dependency injection. This is discussed in the Dependency Injection and Services sections of this guide.

  • $scope上挂载的方法,可以在对应的Controller(作用域)中被访问到,甚至在dom下的angular表达式中也可使用
<div ng-controller="DoubleController">
  Two times <input ng-model="num"> equals {{ double(num) }}
</div>
var myApp = angular.module('myApp',[]);
myApp.controller('DoubleController', ['$scope', function($scope) {
  $scope.double = function(value) { return value * 2; };
}]);
  • $scope中的属性和方法可以被子angular作用域继承。

Since the ng-controller directive creates a new child scope, we get a hierarchy of scopes that inherit from each other. The $scope that each Controller receives will have access to properties and methods defined by Controllers higher up the hierarchy.

初学Vue的一些小细节

var example2 = new Vue({
  el: '#example-2',
  data: {
    name: 'Vue.js'
  },
  // 在 `methods` 对象中定义方法
  methods: {
    greet: function (event) {
      // `this` 在方法里指当前 Vue 实例
      alert('Hello ' + this.name + '!')
      // `event` 是原生 DOM 事件
      if (event) {
        alert(event.target.tagName)
      }
    }
  }
})
// 也可以用 JavaScript 直接调用方法
example2.greet() // -> 'Hello Vue.js!'
  • data属性的字段与methods属性的字段可以通过对应的属性获取,比如example2.name,而可以通过example.$el获取该Vue实例的元素。

  • methods中的this指向当前的Vue实例

  • v-on:click="method"v-on:click="method()"效果一样

  • 可以在template的事件回调函数中使用$event形参获取该原生事件

  • v-bind在Vue中对classstyle有特定的加强,都有对象语法数组语法

<div id="example">
  <div class="static"
     v-bind:class="[computedError,{ active: isActive, 'text-danger': hasError }]" v-bind:style="styleObj">
     Hello Vue;
</div>
var vm = new Vue({
  el: '#example',
  data: {
    message: 'Hello',
    isActive: true,
    hasError: true,
    active: 'activeClass',
    error: 'error',
    styleObj:{
     fontSize: '30px',
     color: 'yellow'
    }
  },
  computed:{
   computedError(){
    return this.error+"-Class"
   }
}
});
<!-- result -->
<!-- class的对象语法中key总为字符串,而class的数组语法的key到data与computed中查找-->
<!-- styleObj在computed或data中只支持camelCase命名-->
<div class="static error-Class active text-danger" style="font-size: 30px; color: yellow;">
     Hello Vue;
</div>
  • v-model对于checkbox来说,当 checkeds 为字符串时,获取的是checked布尔值。当checkeds 为数组时,获取的是value。而在[email protected]中,新增了自定义组件的model属性,可以指定v-model绑定的属性和事件!
<input type="checkbox" id="checkbox1" v-model="checkeds" value="One">
<label for="checkbox1">One</label>
<input type="checkbox" id="checkbox2" v-model="checkeds" value="Two">
<label for="checkbox2">Two</label>
<input type="checkbox" id="checkbox3" v-model="checkeds" value="Three">
<label for="checkbox3">Three</label>
<p>Checked's Value: {{ checkeds }}</p>
Vue.component('my-checkbox', {
  model: {
    prop: 'checked',
    event: 'change'
  },
  props: {
    // this allows using the `value` prop for a different purpose
    value: String
  },
  // ...
})

关于ES6中的解构赋值--解构嵌套对象

function whois({displayName: displayName, fullName: {firstName: name}}){
  console.log(displayName + " is " + name);
}
var user = { 
  id: 42, 
  displayName: "jdoe",
  fullName: { 
      firstName: "John",
      lastName: "Doe"
  }
};
whois(user); // "jdoe is John"

以及

function whois({displayName: displayName,fullName:fullName, fullName: {firstName: name}}){
   console.log(displayName + " is " + name+"fullName:",fullName);
}
var user = { 
  id: 42, 
  displayName: "jdoe",
  fullName: { 
      firstName: "John",
      lastName: "Doe"
  }
};
whois(user); 

fullName:fullName其实是可以获得其中的对象的,不一定是最里面一层。

还有一种少见的情况是,可以利用对象的方括号语法进行解构。

let key = "z";
let { [key]: foo } = { z: "bar" };
console.log(foo); // "bar"

key其实是一个字符串变量,根据变量的值可以解构出需要的属性值。注意[key]中的[]

JavaScript--朝花夕拾のfunction

关于function

命名函数表达式

使用函数很少使用命名函数表达式,它的大致形式如下:

var a = function b(){ }

下面来看三个小栗子:

第一个正常使用的时候,命名的函数变量是无法使用的,生命周期就像是“赋值”后立即“销毁”。所以在函数b的方法体内依然可以调用b自己,所以变量的能否获取是我让大家注意命名函数表达式的第一点(不要在未保存数据的tab尝试自调用的栗子)

var a = function b(){ console.log('function') }
a();// 'function'
b();//Error: b is not defined

第二个使用如下,注意这里是匿名函数

var a = function (){ throw new Error() }
a();// 出错
出错信息如下:
Uncaught Error
    at a (<anonymous>:1:28)
    at <anonymous>:2:1
a @ VM233:1
(anonymous) @ VM233:2

而如果加上命名函数表达式,

var a = function b(){ throw new Error() }
a();// 出错
出错信息如下:
Uncaught Error
    at b (<anonymous>:1:29)
    at <anonymous>:2:1
b @ VM234:1
(anonymous) @ VM234:2

相对于上面的匿名情况,有很好的出错栈信息,能准确地定位到函数b

当然命名函数表达式与函数表达式一样,都无法做到Hosting,这个需要注意。

//TODO

关于puppeteer以及其跳过下载chromium的解决办法

Google最近提供的操作 headless Chromium 的 Puppeteer火了一把。

其实headless brwoser 有很多,常见的就有PhantomJS和Selenium(Selenium算driver,与Puppeteer一样)。如果想知道更多的headless browsers 以及 drivers 可以查看这个Repo,十分的详细。
而Puppeteer是仅仅支持JavaScript语言的Node 库(Puppeteer是一个driver),提供以编程方式操作headless Chromium的高级API。但是注意Puppeteer并不是唯一操作headless Chromeium的库,比如上述链接中提及的chromy

Puppeteer is a Node library which provides a high-level API to control headless Chrome over the DevTools Protocol.

使用Puppeteer 操控 headless Chromium可以完成在浏览器中可以做的很多事情,但是个人觉得最有用处的就是

  1. 自动化测试(模拟输入等)
  2. 爬虫
  3. 单页面应用的SEO优化
  4. 性能分析

而至于Puppeteer与Selenium有什么区别,官方也讲的很清楚:

Selenium / WebDriver is a well-established cross-browser API that is useful for testing cross-browser support.
Puppeteer works only with Chrome. However, many teams only run unit tests with a single browser (e.g. PhantomJS). In non-testing use cases, Puppeteer provides a powerful but simple API because it's only targeting one browser that enables you to rapidly develop automation scripts.
Puppeteer uses the latest versions of Chromium.

Puppeteer只支持JS以及最新版本的Chromium,而Selenium支持跨平台跨语言的API。

而至于headless Chromium与PhantomJS有什么不同?

目前只知道两者内核不同。如果有详细的对比信息,欢迎补充~

昨天刚开始接触Puppeteer,但是安装的使用遇到了问题。由于众所周知的原因,很难下载Chromium,又不知道怎么配置,使得CMD也使用代理。

经过一阵子的折腾,阅读文档和相关的issue,解决办法如下:

  1. 设置npm的环境变量--> npm config -g set PUPPETEER_SKIP_CHROMIUM_DOWNLOAD true,应该也可以设置local的环境变量
  2. 安装Puppeteer--> npm i puppeteer
  3. 下载最新版本的Chromium,注意一定是要Chromium,也就是Chrome Dev Channel,Chrome不可以!!至少我是无法指向已经安装的Chrome 可以使用最新版本的Chrome,当puppeteer提示Error: Timed out after 30000 ms while trying to connect to Chrome! The only Chrome revision guaranteed to work is xxxx的时候,就需要去更新Chrome到最新版本。但是Dev版本有一个不好之处就是API的改动,可能Puppeteer现在可以使用,但不代表之后也可以使用。具体的讨论可以见issue :https://github.com/GoogleChrome/puppeteer/issues/389
  4. 在程序中的launch方法中使用executablePath参数,指向已经安装的ChromiumChrome。比如: const browser = await puppeteer.launch({executablePath:"C:/Program Files (x86)/Google/Chrome Dev/Application/chrome.exe"});

JavaScript--朝花夕拾のwith

关于with

类似的还有with这个家伙,with在花括号内将作用域设置为某个对象,这可以减少我们的代码量,但由此也带来了编译器的负担,导致无法优化。而且如今来看,减少代码量可以使用解构赋值语法来解决

   //without `with`
  var name = person.name;
  var gender = person.gender;
  // with `with`
  with(person){
    var name = name; //会变量提升
    var gender = gender; //会变量提升
 }
  //ES6
  var {name, gender} = person;

再来看看with带来的问题,和eval一样,主要是改变变量的值导致的问题难以被跟踪。

function setName(person) {
  with(person) {
    name = 'name changed!';
  }
}

var name = 'global name';//可有可无

var p1 = {
  name: "p1's name"
};
var p2 = {
  height: 175
};

setName(p1);
console.log('p1.name:', p1.name); // 'p1.name: name changed!

setName(p2);
console.log('p2.name:', p2.name); // p2.name: undefined
console.log('window.name:', name); //window.name: name changed!

可以看到全局的name被改变,仅仅是因为传入的p2不存在name属性,使得沿着作用域查找name,也就是全局的name并赋值,而并不会在传入的person对象中新建一个name属性。即使全局没有var name='global name',也会因为全局没有name而新建window.name不会新建person.name。所以这会让人很困惑!!还不过瘾?我将网上的一个小栗子,包装起来变成大栗子。不知道你会不会头晕?

({
  x: 10,
  foo: function() {
    console.log('foo-->x', x);//undefined
    console.log('foo-->y', y);//undefined
    console.log('foo-->this.x', this.x);//10
    console.log('foo-->this.y', this.y);//undefined

    function bar() {
      console.log('bar-->x', x);//undefined
      console.log('bar-->y', y);//30
      console.log('bar-->this.x', this.x);//20
      console.log('bar-->this.y', this.y);//undefined
    }
    with(this) {
      console.log('with-->this:before',this);//{x:10,foo:f}
      var x = 20;
      var y = 30;
      console.log('with-->this:after',this);//{x:20,foo:f}
      console.log('with-->x', x);//20
      console.log('with-->y', y);//30
      console.log('with-->this.x', this.x);//20
      console.log('with-->this.y', this.y);//undefined
      bar.call(this);
    }
  }
}).foo();

让我们再来看看注释版本的代码:

({
  x: 10,
  foo: function() {
    var x;//初始最外层x
    var y;//初始最外层y
    console.log('foo-->x', x);//undefined,最外层只初始化
    console.log('foo-->y', y);//undefined,最外层只初始化
    console.log('foo-->this.x', this.x);//10,this.x初始值
    console.log('foo-->this.y', this.y);//undefined,this.y并不存在
    function bar() {
      console.log('bar-->x', x);//undefined,在这里并没有with,所以获取的最外层x
      console.log('bar-->y', y);//30,获取的最外层y
      console.log('bar-->this.x', this.x);//20,不解释
      console.log('bar-->this.y', this.y);//undefined,this.y并不存在
    }
    with(this) {
      console.log('with-->this:before',this);//{x:10,foo:f}
      x = 20;//this.x存在,更改,改变的是this.x
      y = 30;//this.y不存在,不会新增,改变的是最外层y
      //要牢记,在with中如果捕获的对象有对应的属性,永远是优先获取该对象的。
      console.log('with-->this:after',this);//{x:20,foo:f},更改成功
      console.log('with-->x', x);//20,获取的仍然是this.x,因为在with中this优先级高于外面的x
      console.log('with-->y', y);//30,this.y并没有,获取最外层的y
      console.log('with-->this.x', this.x);//20,不解释
      console.log('with-->this.y', this.y);//undefined,this.y并不存在
      bar.call(this);
    }
  }
}).foo();

为什么有了String.fromCharCode()还需要String.fromCodePoint()?

两者都是将数值序列转换为String

String.fromCharCode(num1[, ...[, numN]])
String.fromCodePoint(num1[, ...[, numN]])

  • String.fromCharCode()最大支持16位的数字,而且ES中很早就开始支持,兼容性好。而String.fromCodePoint()可以多达21位数字,是ES6新加入的方法,是为了能够处理所有合法的Unicode( in order to deal with ALL legal Unicode values ),因为String.fromCharCode()并不够用。
console.log(String.fromCodePoint(0x1D306, 0x61, 0x1D307)); //𝌆a𝌇
console.log(String.fromCharCode(0x1D306, 0x61, 0x1D307)); //팆a팇

可以看到两者能处理的最大位数是不一样的,String.fromCharCode()并不能正确处理,出现乱码。

提取String子串的三种方法slice()、substr()、substring()的异同?

  • 三者都是根据参数提取String特定子串,返回特定子串,不影响原字符串。

  • 三者的第二个参数默认到字符串结尾的位置。

  • 参数不同。slice(startIndex[,endIndex])substr(startIndex[,strLength])substring(startIndex[,endIndex])

  • slice()substring()虽然签名类似且endIndex并不包含在内,但是作用机制并不一样。前者支持负值(substr中的startIndex也支持负值),但后者不支持负值。如果出现负值则转换为0,而且如果substring()中的endIndex小于startIndex,则会交换位置,使得startIndex小于endIndex。所以,在一定程度上,可以认为相对于slice()substring()具有修正功能

可以看下栗子:

var str = 'The morning is upon us.';
console.log(str.slice(-3));      //  'us.'
console.log(str.slice(-3, -1)); //  'us'
console.log(str.slice(-1,-3)); //   '' [because end is in the  left of start]
console.log(str.slice(-999)); // 'The morning is upon us.'
console.log(str.slice(-1,3)); //'' [because end is in the  left of start]
console.log(str.slice(1,-3)); // 'he morning is upon '

console.log(str.substr(-3));   // 'us.'
console.log(str.substr(-3,-1-(-3))); // 'us'
console.log(str.substr(-1,-1)); // '' [because strLength is negative]
console.log(str.substr(-999)); //'The morning is upon us.'

console.log(str.substring(-3)); //'The morning is upon us.'
console.log(str.substring(-3,-1)); // ''
console.log(str.substring(-1,-3)); // ''
console.log(str.substring(-999))//'The morning is upon us.'
console.log(str.substring(-1,3)); //'The' [equals to str.substring(0,3)]
console.log(str.substring(1,-3)); // 'T' [equals to str.substring(0,1).Firstly,change negative value to zero,and swap the value satisfying that the startIndex smaller than the endIndex]

vscode && git

问题来源和解决方案

被大佬安利了许久的vscode,在前几天终于想尝试着换掉webstorm,用上vscode。vscode确实很强大,强大之处就不列举了,不是今天需要讲的,我们先略过。

在安装好常见的拓展,并且将快捷键设置为自己在webstorm常用的之后,发觉有一些特性并不可以使用。特别是source control部分,再加上前几天一直郁闷的bug(gitlens一直无法工作),现在终于定位到了问题所在!

重点:先说解决方案,下载git-for-windows,然后安装,在vscode的用户设置中,将git.path指向git-for-windows安装目录中的git.exe。

来龙去脉

最开始的问题来自于,旧版本的代码中有这么一段代码:

function findGitWin32(): Promise<IGit> {
	return findSystemGitWin32(process.env['ProgramW6432'])
		.then(void 0, () => findSystemGitWin32(process.env['ProgramFiles(x86)']))
		.then(void 0, () => findSystemGitWin32(process.env['ProgramFiles']))
		.then(void 0, () => findSpecificGit('git'));
}

When git is not installed, that code will trickle down to which will simply do cp.spawn('git'...).
当git没有安装在上述的两个目录中的时候,会执行到findSpecificGit('git'),这个代码会进行执行cp.spawn('git'...),这个会使得node.js遍历PATH中的路径,寻找git相关的命令,而这会导致性能问题,关于问题的描述和相关官方人员的解答看下面的链接:

https://github.com/Microsoft/vscode/issues/32739#issuecomment-341177471

并且在内测版的时候,对vscode的代码进行了一定的修改,让Windows系统中的vscode不再去spawn git,从而不会有crash相关的性能问题。

https://github.com/Microsoft/vscode/commit/fe20886713798fe2acbd8946350edfaa52ac4e5c#diff-e64cb2761ab71b6db409f7e3fb804b14L126

让我们看看目前(2017.12.12)为止,源代码都做了一些什么。代码很长,而且个人不是很懂ts,所以,直接在代码中搜索find,稍微理了一下相关的思路。

有几个相关的方法

  function findSpecificGit(path: string, onLookup: (path: string) => void): Promise<IGit>{}
  function findGitDarwin(onLookup: (path: string) => void): Promise<IGit>{}
  function findSystemGitWin32(base: string, onLookup: (path: string) => void): Promise<IGit>{}
  function findGitWin32(onLookup: (path: string) => void): Promise<IGit>{}
  function findGit(hint: string | undefined, onLookup: (path: string) => void): Promise<IGit>{}

可以知道,findGit是父函数(export可看出。其实也可以在该repo中搜索findGit,发现在同目录的man.ts调用了findGit)。

const pathHint = workspace.getConfiguration('git').get<string>('path');
const info = await findGit(pathHint, path => outputChannel.appendLine(localize('looking', "Looking for git in: {0}", path)));

其余为子函数。可以发现这些函数的签名都有onLookup,结合在main.ts调用的findGit实参和形参命名来看,onLookup是寻找git过程中的一个回调方法,而这些方法也都是返回的Promise对象。

从findGit的调用开始看起,可以发现和我们的用户配置(settings.json)文件相关(git.path)。如果git.path没有配置,则会根据process.platform执行相关的方法,比如在windows中会执行findGitWin32(onLookup),会执行findSystemGitWin32查找系统的git.exe(ProgramW6432=C:\ProgramFiles)。

//环境变量的解释见:https://stackoverflow.com/questions/17688758/using-programfilesx86-on-windows-os-32bit
{
   ProgramFiles: 'C:\\Program Files (x86)',
  'ProgramFiles(x86)': 'C:\\Program Files (x86)',
  ProgramW6432: 'C:\\Program Files',
}

综合来看,vscode默认windows系统常见的git路径有这2-3个(在上述路径中拼接上Git/cmd/git.exe
(不知道是出于什么考虑,估计是常见的软件?原本是有findGitHubGitWin32的,但是由于一些bug,代码中移除了,所以基于这个考虑),如果找不到,则会抛出not found异常。
下面的第一张图可以看出,注释掉设置中的git.path,虽然有输出Git的信息,但是左边并没有活动的源代码管理程序。这是因为输出用的是我MSYS2中的git的(PATH中有该路径,并且在windows git之前),因为git的version是2.5.0,而左边的sidebar是使用的上面所说的查找路径的git。可以见图二的git version比较
图一

图二

而当我们把用户设置中的git移除并重新加载vscode的时候,可以发现,果然生效了!!!
image

可能会有人问,在PATH中加入git的执行路径还是不可以吗?
PATH中的只会影响输出,在新版本中并不会被vscode的git插件使用。在旧版本确实是可以使用的,至于为什么新旧版本不同,原因上面讲过了---就是spawn查找时间过长导致性能问题,vscode换了另外一种方式

两全其美?

虽然知道可以用git.path指向git-for-windows的git执行路径即可解决这个问题。但是又有了新的问题。

比如说我,在Windows中使用MSYS2和vscode,当然希望两者尽量得结合,而尽可能不再安装其他的软件,因为MSYS2中都已经有Git了,命令行那么好用,我再去找安装包那不是没事找事吗。

所以我们来尝试着使用git.path指向MSYS2中的git.exe。不好意思,并不可以。会一直报下面的错误。至于解决办法我目前也无法知道,如果有解决办法希望指出。所以git for windows在目前而言是必不可少的。两全其美至少在现在是,不存在的。

fatal: Not a git repository (or any parent up to mount point /)
Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set).

Vue文档中的字符串模板和HTML模版的区别?

在Vue的官方文档多处可以看到,字符串模板与非字符串模板(DOM模板或HTML模板),比如is属性的使用:

自定义组件 被认为是无效的内容,因此在渲染的时候会导致错误。变通的方案是使用特殊的 is 属性:

<table>
  <tr is="my-row"></tr>
</table>

应当注意,如果您使用来自以下来源之一的字符串模板,这些限制将不适用:

  • <script type="text/x-template">(x-template)
  • JavaScript内联模版字符串
  • .vue 组件

其实辨别没有那么复杂,在*.html文件中的以标签形式挂载上DOM都算DOM模版,而其他形式算字符串模板,比如:

Vue.component('c-first',{
 template: '<div>c-first用在了字符串模板中!</div>'
});
new Vue({
  el: '#app',
  data: {
    htmlStr: '<cFirst>c-first的使用算字符串模板</cFirst>'
  }
})
<div id="app">
  <div v-html="htmlStr"></div>
</div>

而如下就不算字符串模板,它同样是DOM挂载

<div id="app">
  <c-second></c-second>
</div>
Vue.component('c-first',{
 template: '<div><slot></slot></div>'
});
Vue.component('c-second',{
 template: '<cFirst>cFirst的使用**不算**字符串模板,提示未定义该cFirst组件,应使用c-first</cFirst>'
}); 

-EOF-

JavaScript--朝花夕拾のeval

在ES8发布了一段时间的今天(2017-08-10),还有理由用ES5乃至ES3的语法?还需要用eval等不讨好的语法?

emm...不用不代表不需要整理一下一些知识点。算是一种抄冷饭?嗨,不管先,学习最重要,更重要的是将知识点串联起来,变得更系统。

关于eval以及fake eval

关于使用eval的坏处,就不需要我多说了吧?主要是安全问题(脚本攻击),当然还有因为变量的不确定性导致的性能问题(原本的形式会预编译稳定和可预测的变量),不过对于如今的JavaScript引擎而言并不是很明显。至于大家说的调试困难,大多应该说的是改变的逻辑太跳以及语义不明,接下来的with也是,就有点像goto的类比。但至于说无法进入代码进行调试,大家可以试试下面的代码,是可以进入调试的,只不过当行数多了就不好把控。

function a(){eval('debugger;var b = 2;console.log(b)')}a();

上面说到eval导致的变量不确定(也涉及到调试困难),如何体现?

//栗子来自@三生石上
var foo = 1;
 function test() {
        var foo = 2;
       var bar = eval;
        bar('foo = 3');//本来我是可以确切地知道全局中的foo是为1的,然而你在这里给我改了?!(非直接调用)
        return foo;
 }
foo;//1
test(); // 2
 foo; // 3

在不同的作用域非直接地调用,会改变全局对象的变量。而直接调用eval,会改变调用时的所在作用域的变量。

//和上面一个例子形成对照
var foo = 1;
function test() {
        var foo = 2;
        eval('foo = 3');
        return foo;
 }
foo; //1
test(); // 3
foo; // 1

在不同的作用域非直接地调用,会改变全局对象的变量

这也就可以解释setTimeoutsetInterval中的第一个参数为字符串时候,会时常出现找不到变量的异常了,因为此时调用的时候是到全局中寻找相关的函数变量,比如fakeClick

(function(){
  var fakeClick = function(){
    console.log('fakeClick!')
  };
  setTimeout('fakeClick',1000);
})()
//outputs: Uncaught ReferenceError: fakeClick is not defined

eval还有哪些需要注意的点?还有一个小细节是在不经意间看到stackoverflow的--eval在当时(JSON.parse()未出现时)常被提及可以用来解析json数据,将其转换为object类型。大致的用法是eval('('+data+')'),data之间之所以有()是因为对象字符串有{},会导致eval将其解析为一个表达式,类似的栗子还有IIFE立即执行函数。

而我要说的不是这个,是eval并不能解析一些特定的token,比如\u2028

var data = '{"name":"geekaholic"}';
eval('('+data+')');//正常
JSON.parse(data);//正常
var data = '{"name":"\u2028"}';
eval('('+data+')');//出错,Invalid or unexpected token
JSON.parse(data);//正常,{name: "
"}

-EOF-

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.