Skip to content

Instantly share code, notes, and snippets.

@simonid
Last active March 11, 2018 15:53
Show Gist options
  • Save simonid/9a02b15613c8629d8237c7c5a3de44d8 to your computer and use it in GitHub Desktop.
Save simonid/9a02b15613c8629d8237c7c5a3de44d8 to your computer and use it in GitHub Desktop.
JavaScript随笔

JavaScript字符串截取函数slice、substring、substr

substring

功能:返回一个索引和另外一个索引之间的字符串
语法:str.substring(start,[end])

注意:

  • 截取范围包括start但是不包括end
  • 若start==end,则返回空
  • end可省略,如果省略则表示从start截取到字符串末尾
  • 若某个参数是NaN或者小于0,那么等效为0;若某个参数大于字符串长,那么等效为字符串长
  • 若start大于end,那么start和end交换,比如str.substring(2,0) == str.substring(0,2)

demo:

var str = 'abcdefghij';
console.log('(1, 2): '   + str.substring(1, 2));   // '(1, 2): b'
console.log('(1, 1): '   + str.substring(1, 1));   // '(1, 1): '
console.log('(-3, 2): '  + str.substring(-3, 2));  // '(-3, 2): ab'
console.log('(-3): '     + str.substring(-3));     // '(-3): abcdefghij'
console.log('(1): '      + str.substring(1));      // '(1): bcdefghij'
console.log('(-20, 2): ' + str.substring(-20, 2)); // '(-20, 2): ab'
console.log('(2, 20): '  + str.substring(2, 20));  // '(2, 20): cdefghij'
console.log('(20, 2): '  + str.substring(20, 2));  // '(20, 2): cdefghij'

substr

功能:返回从指定位置开始的字符串中指定字符数的字符串
语法:str.substr(start,[length])


注意:
  • 它会从start位置截取长度为length的字符串
  • 若start是正数且大于或等于字符串长度,则返回一个空字符串
  • 若start为负数,则将该值加上字符串长度再进行计算,若还是负数,则从0开始截取(实际上就是倒过来从尾部开始截取)
  • 若length为0或负数,则返回空;若length省略,则截取到末尾

demo:

var str = 'abcdefghij';
console.log('(1, 2): '   + str.substr(1, 2));   // '(1, 2): bc'
console.log('(-3, 2): '  + str.substr(-3, 2));  // '(-3, 2): hi'
console.log('(-3): '     + str.substr(-3));     // '(-3): hij'
console.log('(1): '      + str.substr(1));      // '(1): bcdefghij'
console.log('(-20, 2): ' + str.substr(-20, 2)); // '(-20, 2): ab'
console.log('(20, 2): '  + str.substr(20, 2));  // '(20, 2): '

slice

功能:返回一个索引到另一个索引间的字符串
语法:str.slice(start,[end])

注意:

  • start和end都为正数时,截取范围不包括start和end
  • 若start为负数,start=start+length,若还是负数,则从0开始截取(实际上就是倒过来从尾部开始截取)
  • 若start大于或等于字符串长,则返回空
  • 若end省略,则截取到字符串末尾;若end为负数,则end=length+end

demo:
var str = 'abcdefghij';
console.log('(1, 2): '   + str.slice(1, 2));   // '(1, 2): b'
console.log('(-3, 2): '  + str.slice(-3, 2));  // '(-3, 2): '
console.log('(-3, 9): '  + str.slice(-3, 9));  // '(-3, 9): hi'
console.log('(-3): '     + str.slice(-3));     // '(-3): hij'
console.log('(-3,-1): ' + str.slice(-3-1));     // '(-3,-1): hi'
console.log('(0,-1): '  + str.slice(0-1));     // '(0,-1): abcdefghi'
console.log('(1): '      + str.slice(1));      // '(1): bcdefghij'
console.log('(-20, 2): ' + str.slice(-20, 2)); // '(-20, 2): ab'
console.log('(20): '     + str.slice(20));  // '(20): '
console.log('(20, 2): '  + str.slice(20, 2));  // '(20, 2): '

补充:

注意和split的区分

split是将一个字符串分割为字符串数组

demo:

"hello".split("")	//可返回 ["h", "e", "l", "l", "o"]
"2:3:4:5".split(":")	//将返回["2", "3", "4", "5"]

参考:
[掘金-JS字符串截取函数slice(),substring(),substr()的区别](https://juejin.im/post/59e2af3151882578cf573319)

JavaScript中for...of...和for...in...的区别

二者都是迭代的方法,区别在于迭代方式
迭代的概念可以看作遍历

最早数组遍历的方式:

var arr = [1,2,3];
for(var i=0;i<arr.length;i++)
    console.log(arr[i]);

ES5后引入了forEach

var arr = [1,2,3];
arr.forEach(function(a){
    console.log(a);
});

虽然简洁了一些,不过还是存在一些缺陷的:
1.不能使用break退出循环
2.不能使用return返回内容到外层

所以我们想到了for...offor...in两个语句

说明:let...offor...of等效;let...infor...in等效

for...in语句以任意顺序遍历一个对象的可枚举属性(表面上循环结果是key)。对于每个不同的属性,语句都会被执行。

var a = ["a", "b", "c"];
a.name = 'test';
for(var index in a){
  console.log(a[index]);
  //console.log(typeof index);
}
//a  b  c  test

上述代码正确地将数组内的元素遍历了出来,但是同时将数组的属性也一并打印出来了

因此可以看出,作用于数组的for-in循环除了遍历数组元素以外,还会遍历自定义属性

同时要注意:
for...in是按照随机顺序遍历数组的;假如数组内元素是String,可能会无意间进行字符串运算

为了解决前面方法的不足,ES6引入了for...of

for...of语句在可迭代对象(包括 Array,Map,Set,String,TypedArray,arguments 对象等等)上创建一个迭代循环,调用自定义迭代钩子,并为每个不同属性的值执行语句。

var a = ["a", "b", "c"];
a.name = 'test';
for(var value of a){
  console.log(value);
  //console.log(typeof value);
}
//a  b  c 

for...in相反,它不会循环出对象的key,只会循环出数组的value,因此它不能循环遍历普通对象。 不过假如和Object.keys()一起使用,可以线获取对象的所有key的数组,然后再遍历出value

var student={
    name:'wujunchuan',
    age:22,
    locate:{
    country:'china',
    city:'xiamen',
    school:'XMUT'
    }
}
for(var key of Object.keys(student)){
    //使用Object.keys()方法获取对象key的数组
    console.log(key+": "+student[key]);
}
//不好的方式

for(var prop in  student){
  console.log(prop+': '+student[prop]);
}
//好的方式

for...in的另外一个对比示例:

Object.prototype.objCustom = function() {}; 
Array.prototype.arrCustom = function() {};

let iterable = [3, 5, 7];
iterable.foo = 'hello';
/*
每个对象将继承objCustom属性,并且作为Array的每个对象将继承arrCustom属性,因为将这些属性添加到Object.prototype和Array.prototype。
由于继承和原型链,对象iterable继承属性objCustom和arrCustom。
*/

for (let i in iterable) {
  console.log(i); // logs 0, 1, 2, "foo", "arrCustom", "objCustom"
}
/*
此循环仅以原始插入顺序记录iterable 对象的可枚举属性。它不记录数组元素3, 5, 7 或hello,因为这些不是枚举属性。
但是它记录了数组索引以及arrCustom和objCustom。
*/

for (let i in iterable) {
  if (iterable.hasOwnProperty(i)) {
    console.log(i); // logs 0, 1, 2, "foo"
  }
}
/*
这个循环类似于第一个,但是它使用hasOwnProperty() 来检查,如果找到的枚举属性是对象自己的(不是继承的)。
如果是,该属性被记录。记录的属性是0, 1, 2和foo,因为它们是自身的属性(不是继承的)。
属性arrCustom和objCustom不会被记录,因为它们是继承的。
*/

for (let i of iterable) {
  console.log(i); // logs 3, 5, 7
}
/*
该循环迭代并记录iterable作为可迭代对象定义的迭代值,这些是数组元素 3, 5, 7,而不是任何对象的属性。
*/

总结一下

  • for...in 语句以原始插入顺序迭代对象的可枚举属性。 for...of 语句遍历可迭代对象定义要迭代的数据。
  • for...in循环出的是key,for...of循环出的是value
  • for...of不能循环普通的对象,需要通过和Object.keys()搭配使用。此时用for...in就很好

参考:
MDN - for...of语句
MDN - for...in语句
SegmentFault - javascript总for of和for in的区别?
Javascript中的for-of循环

JavaScript中的this

this永远指向它执行的上下文环境,无论是apply、call、bind,都是为了给this指定上下文环境

不同作用域下的this

全局作用域下的this

this在函数之外,无论是否为严格模式,都是指向window
所以this===window

函数作用域下的this

函数作用域内的this情况较为复杂,但是总体而言,this取决于函数如何调用。只有在函数被调用时,才存在this的指向

函数直接调用

普通模式下,直接调用函数,里面的this指向window

 var t = 1;
 function test(){
    var t = 2;
    console.log(this.t);
}
test();  //1


(function test(){
  //"use strict"; 严格模式下this指向undefined
  console.log(this) // window
})();

严格模式下,直接调用函数,内部this指向该函数运行的环境

function test(){
    "use strict";
    return this;
}
console.log(test());  //undefined

上述demo中,尽管函数是在window下直接运行,但是由于没有被调用而是直接运行,左移this是undefined
假如改成下面:

function test(){
  "user strict" //严格模式
  return this;
}
console.log(window.test()) //window

使用了window来调用函数,那么函数执行环境就是window

所以在函数作用域中,通过什么对象来调用方法,运行环境就是该对象所在的上下文,方法内的this指向该对象

就比如 window.test() ,运行环境就是window所在是上下文

对象中的this

当对象调用自己内部方法,this指向该对象

var obj = {
  id: 9,
  test: function() {
    return this.id;
  }
};
console.log(obj.test()); //9

这个demo中,test方法是对象obj内部的方法,通过obj调用test方法,那么该函数的运行环境就是obj所在的上下文,因而this指向obj

如果是按照匿名函数的方式调用,依旧是指向对象:

var obj = {id: 9};
function test() {
  return this.id;
}
obj.t = test;
console.log(obj.t()); // logs 9

原型链中的this

例子:

var o = {
  f : function(){ 
    return this.a + this.b; 
  }
};
var p = Object.create(o);
p.a = 1;
p.b = 4;
console.log(p.f()); // 5

由前面提到的执行环境,在对象o没有被调用的时候,函数f内的this是没有指向的
当创建了一个继承自o的新对象p,那么p同时继承了f,通过调用p.f(),则this的执行环境指向对象p

getter和setter中的this

get用来获取对象属性,set就是设置对象属性

var obj = {
  a: 1,
  b: 2,
  get test(){
    return this.a + this.b;
  },
  set f(x) {
    this.a = x;
    this.b = x + 1;
  }
};
console.log(obj.test) //3
obj.f = 5
console.log(obj.test) //11

当对象obj调用自身方法时,也就是obj.test(),obj是test方法的运行环境,this指向obj。
通过set设置对象obj属性后,a和b分别变为5和6,之后再调用obj.test。但是obj依旧是test方法的运行环境, 所以无论怎么调用对象内部函数中的this,依旧坚持指向运行环境,也就是对象本身,当然其他非继承的对象也无法调用其内部的方法,所以this可以看作只认一个爸爸

构造函数内的this

function Templete(){
  this.a = 8;
}
var t = new Templete();
console.log(t.a); // 8

new实例化的对象就是一个运行环境,所以构造函数中的this就是指向实例化的对象

call和apply中的this

call和apply方法第一个参数就是绑定函数的运行环境

function add(x) {
  return this.a + x
}
var obj = {
  a: 10
}
var sum = add.call(obj, 8)
//var sum = add.apply(obj, [8])
console.log(sum) // 18

这里,我们定义了一个函数add,用来求a+x,但是a是this.a指代的,如果直接运行add(x),无法给出a的值,此时就需要apply和call了
知道了this.a从何而来,我们就可以通过apply和call将add里面的this绑定到obj对象中
所以这里add方法内的thi指向obj

bind中的this

它的第一个参数也是用来绑定this指向的对象

function f(x){
  return this.a + x;
}
var obj = {
  a: 10
}
var sum = f.bind(obj, 8);
console.log(sum()) // 18

DOM事件函数中的this

假如我们监听一个DOM元素,给该DOM元素绑定一个事件,那么事件函数内的this指向当前的DOM元素

var ele = document.getElementById('tab');
ele.addEventListener('click', func, false);
function func(e) {
    console.log(this === e.target) //true
}

内联事件中this

这种情况和DOM事件函数的情况一样,也是指向对应的DOM元素
<div onclick="alert(this.tagName.toLowerCase())"></div>
打印出来的就是当前div标签

概括总结

  1. 全局范围:它会指向全局对象( 浏览器下指window)
  2. 全局函数调用:它 还是指向全局对象。
  3. 对象函数调用:调用某个对象的函数,它指向当前对象
  4. 使用 new 实例化对象时:它指向 新创建的对象。
  5. 调用某些方法时:如: Function.prototype 上的 call 或者 apply 方法 以及 with等它指向传入的对象

参考:
知乎 - 如何理解 JavaScript 中的 this 关键字?
如何理解 JavaScript 中的 this 关键字? - 二月的回答 - 知乎
如何理解 JavaScript 中的 this 关键字? - 萧强的回答 - 知乎


下面是补充:

关于setTimeout和setInterval中this指向问题

在慕课网TypeScript入门课程中,练习一个小demo发现了问题,:

function getStock(name){
	this.name = name;
	setInterval(function(){
		console.log("name1 is:"+this.name);
	},2000);
}
var stock = new getStock("test");

上述最后的输出结果不是期待中的test,而是undefined,说明此时的this指向了window对象
原因就在于类似setInterval这样的函数在调用代码时运行在域所在函数完全分离的执行环境上。
要解决这个问题,可以参考下面的3个方法:

###1.将当前对象的this保存为一个变量,然后定时器内的函数用闭包访问该变量

function getStock(name){
  var that = this;
	this.name = name;
	this.getfunc = function(){ 
  	setInterval(function(){
  		console.log("name1 is:"+that.name);
  	},2000);
  }
}
var stock = new getStock("test");
stock.getfunc();   
//test

function getStock(name){
  var that = this;
	this.name = name;
	(function(){ 
  	setInterval(function(){
  		console.log("name1 is:"+that.name);
  	},2000);
  })()
}
var stock = new getStock("test");
//test

###2.用bind改绑定this

function getStock(name){
	this.name = name;
  	setInterval(function(){
  		console.log("name1 is:"+this.name);
  	}.bind(this),2000);
}
var stock = new getStock("test");

当被绑定函数执行时,bind会创建一个新函数,并将第一个参数作为新函数运行的this。
同时,也可以使用apply和call方法,不过要注意call会在调用后立即执行,相当于失去延时效果

###3.箭头函数

function getStock(name){
	this.name = name;
  	setInterval(() => {
  		console.log("name1 is:"+this.name);
  	},2000);
}
var stock = new getStock("test");

ES6标准中的箭头函数的this总是指向词法作用域,也就是外层调用者

参考:
关于setInterval和setTImeout中的this指向问题

慕课网课程关于上下文环境的教程

[慕课网课程关于上下文环境的教程(https://www.imooc.com/video/7558)

this的使用:

*1.作为对象的方法 this在方法内部,this就指向调用这个方法的对象

var pet = {
  words = 'miao',
  speak : function(){
    console.log(this.words);
    console.log(this === pet);
  }
}
pet.speak();
//miao
//true

*2.函数的调用 this指向执行环境中的全局变量(浏览器->window || nodejs ->global)

function pet(words){
  this.words = words;
  console.log(this.words);
  console.log(this);
}
pet('miao');

//miao
//window

3.构造函数 this所在的方法被实例对象所调用,那么this就指向这个实例对象

function pet(words){
  this.words = words;
  this.speak = function(){
    consoel.log(this.words);
  }
}

var cat = new pet('miao');
cat.speak();

//miao

继承

function pet(words){
  this.words = words;
  this.speak = function(){
    console.log(this.words);
  }
}

function dog(words){
  pet.call(this,words);
  pet.apply(this,arguments);
}

var dog = new dog('wang');
dog.speak();
//wang

JavaScript随笔

说明: 本篇是JavaScript的一些零散的知识点汇集

Array.prototype.slice.call(arguments)将对象转换为数组

Array.prototype.slice.call(arguments)能将具有length属性的对象转成数组,出来IE的节点集合

首先,slice([begin],[end])方法返回一个从开始到结束(不包括结束)选择的数组的一部分浅拷贝到一个新数组对象。 原始数组内容不变

var arr = ['a','b','c','d'];
console.log(arr.slice(1,3));
//['b','c']

slice 不修改原数组,只会返回一个浅复制了原数组中的元素的一个新数组。原数组的元素会按照下述规则拷贝:

  • 如果该元素是个对象引用 (不是实际的对象),slice 会拷贝这个对象引用到新的数组里。两个对象引用都引用了同一个对象。 如果被引用的对象发生改变,则新的和原来的数组中的这个元素也会发生改变。
  • 对于字符串、数字及布尔值来说(不是 String、Number 或者 Boolean 对象),slice 会拷贝这些值到新的数组里。 在别的数组里修改这些字符串或数字或是布尔值,将不会影响另一个数组。

将类似数组的对象/集合转换为数组

function list() {
  return Array.prototype.slice.call(arguments);
}

var list1 = list(1, 2, 3); // [1, 2, 3]

除了使用 Array.prototype.slice.call(arguments),你也可以简单的使用 [].slice.call(arguments) 来代替。 另外,你可以使用 bind 来简化该过程。

var unboundSlice = Array.prototype.slice;
var slice = Function.prototype.call.bind(unboundSlice);

function list() {
  return slice(arguments);
}

var list1 = list(1, 2, 3); // [1, 2, 3]

通用函数

var toArray = function(s){
    try{
        return Array.prototype.slice.call(s);
    } catch(e){
            var arr = [];
            for(var i = 0,len = s.length; i < len; i++){
                //arr.push(s[i]);
                   arr[i] = s[i];  //据说这样比push快
            }
             return arr;
    }
}

参考:
MDN - Array.prototype.slice()
CSDN - Array.prototype.slice.call()方法详解

JavaScript实现拖拽

在写代码前,先分析拖拽组件需要什么相关事件以及涉及到的位置属性

拖拽的动作

拖拽某个目标,首先是在指定目标上按下鼠标(mousedown),确定了按下鼠标的状态和拖动前目标的位置后, 接着就是移动鼠标(mousemove),在移动的过程中,需要时刻改变目标的位置。 松开鼠标(mouseup)后,拖拽完成,并且重置拖拽状态。

注意,mousemove不能绑定在element上,一旦这样做, 鼠标点击在方块内之后,然后快速地拖拽方块(方块的面积是有限的),鼠标的焦点会跑离方块(肉眼上看上去方块跟不上鼠标的快速移动)。 松开鼠标后移动焦点回方块,不需要点击,方块也会跟焦点着移动。
所以这里mousemove需要绑定在document上(鼠标拖动再快也不至于脱离整个页面吧)


位置属性

我们知道,JavaScript有很多的位置属性,在这里使用到了offsetXoffsetY, 点击时position.offsetX记录的就是鼠标焦点相对于目标的位置。 然后在mousemove事件中设置element.syle.left=event.clientX-position.offset,获取目标的移动距离

<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8">
	<meta http-equiv="X-UA-Compatible" content="IE=edge">
	<title>简易的拖拽</title>
	<style>
		#block {
            width: 200px;
            height: 200px;
            background-color: #000;
            position: absolute;
        }
	</style>
</head>
<body>
	<div id="block"></div>

	<script>
		function Drag(id){
			var element = document.getElementById(id);
			var position = {
				offsetX : 0,
				offsetY : 0,
				status : 0
			};

			element.addEventListener('mousedown',function(event){
				var e = event || window.event;
				position.offsetX = e.offsetX;
				position.offsetY = e.offsetY;
				position.status = 1;
			},false);

			document.addEventListener('mousemove',function(event){
				var e = event || window.event;
				if(position.status){
					position.endX = e.clientX;
					position.endY = e.clientY;
					element.style.position = 'absolute';
					element.style.top = position.endY - position.offsetY + 'px';
					element.style.left = position.endX - position.offsetX + 'px';
				}
			},false);

			element.addEventListener('mouseup',function(event){
				position.status = 0;
			},false);
		}

		Drag('block');
	</script>
</body>
</html>

本文是有关JavaScript的一些零散的笔记

判断一个变量是否为数组

常用的方法:

var arr = [];

//isArray方法
Array.isArray(arr);

//instanceof
arr instanceof Array;

//constructor
arr.constructor === Array

//Array.prototype.isPrototypeOf()
Array.prototype.isPrototypeOf(arr);

//Object.getPrototypeOf()
Object.getPrototypeOf(arr) === Array.prototype

//Object.prototype.toString.apply()
Object.prototype.toString.apply(a) === '[object Array]'

一般来说,多数主流框架都使用最后一种方法判断。另外要补充,这种方法不仅能判断是否为数组,还能判断是否为字符串等等。
demo:

Object.prototype.toString.call(2);
Object.prototype.toString.call(new Object);

split、slice、splice的分辨

  • slice: 不会修改原来的数组,截取数组元素返回

语法:`slice(start,[end])`
    var arr1 = ["a", "b", "c", "d", "e", "f"];
    // 从下标为0的位置开始截取,截取到下标2,但是不包括下标为2的元素. 原数组没有任何的变化
    var newArr = arr1.slice(0, 2);
    alert(newArr);// a, b
    alert(arr1.slice(1, 4)); // b,c,d
    //从下标为2的元素开始截取,一直到最后一个元素
    alert(arr1.slice(2));  //c,d,e,f
    //从倒数第5个元素,截取到倒数第2个
    alert(arr1.slice(-5, -2)); // b c d
  • splice: 直接修改原数组,删除或替换原数组中的指定元素,返回的是被删除或替换的元素组成的数组(注意区分返回值和被操作值)

var color = new Array('red','blue','yellow','black');

console.log(color.splice(1,2));  //删除color中的1、2两项
//"blue", "yellow"

color.splice(1,0,'brown','pink');  //在color键值为1的元素前插入两个值
console.log(color);
//"red", "brown", "pink", "blue", "yellow", "black"

console.log(color.splice(1,2,'brown','pink'))
//"blue", "yellow"
console.log(color)
//"red", "brown", "pink", "black"
  • split: 根据特定的字符切割字符串并且返回生成的数组

str = "Hello!World";
str2 = str.split("!");
console.log(str2);  //Hello,World
console.log(str2.length);  //2

数组去重

indexof:

function unique(arr){
  var a = []
  for(var i=0;i<arr.length;i++){
    if(a.indexOf(a[i]) == -1){
      a.push(arr[i]);
    }
  }
  return a;
}

但是这个方法不能识别有NaN的方法,比如有var a = [1,1,2,3,4,NaN,NaN];,最后得出的结果是 [1, 2, 3, 4, NaN, NaN]

filter+indexof:

function uniuqe(arr){
  var res = arr.filter(function(item,index,array){
    return array.indexOf(item) === index;
  })
  return res;
}

//升级版:排序去重
function unique(arr){
  return arr.concat().sort().filter(function(item,index,array){
    return !index || item !== array[index-1]
  })
}

这个方法本质上也是用indexOf,所以也不能识别NaN

ES6的新方法

ES6提供了Set数据结构,它类似于数组(具有 iterable 接口),但是其成员是唯一的,利用这个特效可以实现数组去重

function unique(arr){
  return arr.from(new Set(arr));
}

//更加简化
function unique(arr){
  return [...new Set(arr)];
}
//最残暴的简化
var unique = (a) => [...new Set(a)]

在前面提到了ES5的几种方法中均认为内部的多个NaN都是不同的,但是在ES6中,在Set内部,两个NaN是相等 所以,就可以去除重复的NaN

ES6还有一种数据结构Map,同样可以用来实现:

function unique(arr){
  const m = new Map();
  return arr.filter((a) => !m.has(a) && m.set(a,1))
}

对于Map结构,如果对同一个键多次赋值,后面的值将覆盖前面的值。
所以,也可以实现NaN去重


参考:

Github-JavaScript专题之数组去重
数组去重

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment