markdown JavaScript的随笔
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了markdown JavaScript的随笔相关的知识,希望对你有一定的参考价值。
# JavaScript实现拖拽
在写代码前,先分析拖拽组件需要什么相关事件以及涉及到的位置属性<br>
## 拖拽的动作
拖拽某个目标,首先是在指定目标上按下鼠标(mousedown),确定了按下鼠标的状态和拖动前目标的位置后,
接着就是移动鼠标(mousemove),在移动的过程中,需要时刻改变目标的位置。
松开鼠标(mouseup)后,拖拽完成,并且重置拖拽状态。
<br>
注意,`mousemove`不能绑定在element上,一旦这样做,
鼠标点击在方块内之后,然后快速地拖拽方块(方块的面积是有限的),鼠标的焦点会跑离方块(肉眼上看上去方块跟不上鼠标的快速移动)。
松开鼠标后移动焦点回方块,不需要点击,方块也会跟焦点着移动。<br>
所以这里`mousemove`需要绑定在`document`上(鼠标拖动再快也不至于脱离整个页面吧)
<br>
## 位置属性
我们知道,JavaScript有很多的位置属性,在这里使用到了`offsetX`和`offsetY`,
点击时`position.offsetX`记录的就是鼠标焦点相对于目标的位置。
然后在`mousemove`事件中设置`element.syle.left=event.clientX-position.offset`,获取目标的移动距离
<br>
```html
<!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中的this
this永远指向它执行的上下文环境,无论是apply、call、bind,都是为了给this指定上下文环境<br>
## 不同作用域下的this
### 全局作用域下的this
this在函数之外,无论是否为严格模式,都是指向window<br>
所以`this===window`<br>
### 函数作用域下的this
函数作用域内的this情况较为复杂,但是总体而言,this取决于函数如何调用。只有在函数被调用时,才存在this的指向<br>
#### 函数直接调用
普通模式下,直接调用函数,里面的this指向window
```javascript
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指向该函数运行的环境<br>
```javascript
function test(){
"use strict";
return this;
}
console.log(test()); //undefined
```
上述demo中,尽管函数是在window下直接运行,但是由于没有被调用而是直接运行,左移this是undefined<br>
假如改成下面:<br>
```javascript
function test(){
"user strict" //严格模式
return this;
}
console.log(window.test()) //window
```
使用了window来调用函数,那么函数执行环境就是window<br>
所以在函数作用域中,通过什么对象来调用方法,运行环境就是该对象所在的上下文,方法内的this指向该对象<br>
就比如 `window.test()` ,运行环境就是window所在是上下文<br>
#### 对象中的this
当对象调用自己内部方法,this指向该对象<br>
```javascript
var obj = {
id: 9,
test: function() {
return this.id;
}
};
console.log(obj.test()); //9
```
这个demo中,test方法是对象obj内部的方法,通过obj调用test方法,那么该函数的运行环境就是obj所在的上下文,因而this指向obj<br>
如果是按照匿名函数的方式调用,依旧是指向对象:<br>
```javascript
var obj = {id: 9};
function test() {
return this.id;
}
obj.t = test;
console.log(obj.t()); // logs 9
```
#### 原型链中的this
例子:<br>
```javascript
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是没有指向的<br>
当创建了一个继承自o的新对象p,那么p同时继承了f,通过调用p.f(),则this的执行环境指向对象p<br>
#### getter和setter中的this
get用来获取对象属性,set就是设置对象属性<br>
```javascript
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。<br>
通过set设置对象obj属性后,a和b分别变为5和6,之后再调用obj.test。但是obj依旧是test方法的运行环境,
所以无论怎么调用对象内部函数中的this,依旧坚持指向运行环境,也就是对象本身,当然其他非继承的对象也无法调用其内部的方法,所以this可以看作只认一个爸爸<br>
#### 构造函数内的this
```javascript
function Templete(){
this.a = 8;
}
var t = new Templete();
console.log(t.a); // 8
```
<b>new实例化的对象就是一个运行环境,所以构造函数中的this就是指向实例化的对象</b><br>
#### call和apply中的this
call和apply方法第一个参数就是绑定函数的运行环境<br>
```javascript
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了<br>
知道了this.a从何而来,我们就可以通过apply和call将add里面的this绑定到obj对象中<br>
所以这里add方法内的thi指向obj<br>
#### bind中的this
它的第一个参数也是用来绑定this指向的对象<br>
```javascript
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元素<br>
```javascript
var ele = document.getElementById('tab');
ele.addEventListener('click', func, false);
function func(e) {
console.log(this === e.target) //true
}
```
#### 内联事件中this
这种情况和DOM事件函数的情况一样,也是指向对应的DOM元素<br>
`<div onclick="alert(this.tagName.toLowerCase())"></div>`<br>
打印出来的就是当前div标签<br>
## 概括总结
<ol>
<li>全局范围:它会指向全局对象( 浏览器下指window)</li>
<li>全局函数调用:它 还是指向全局对象。</li>
<li>对象函数调用:调用某个对象的函数,它指向当前对象</li>
<li>使用 new 实例化对象时:它指向 新创建的对象。</li>
<li>调用某些方法时:如: Function.prototype 上的 call 或者 apply 方法 以及 with等它指向传入的对象</li>
</ol>
参考:<br>
[知乎 - 如何理解 JavaScript 中的 this 关键字?](https://www.zhihu.com/question/19636194)<br>
[如何理解 JavaScript 中的 this 关键字? - 二月的回答 - 知乎](https://www.zhihu.com/question/19636194/answer/153576642)<br>
[如何理解 JavaScript 中的 this 关键字? - 萧强的回答 - 知乎](https://www.zhihu.com/question/19636194/answer/25081397)<br>
<hr>
下面是补充:<br>
## 关于setTimeout和setInterval中this指向问题
在慕课网TypeScript入门课程中,练习一个小demo发现了问题,:<br>
```javascript
function getStock(name){
this.name = name;
setInterval(function(){
console.log("name1 is:"+this.name);
},2000);
}
var stock = new getStock("test");
```
上述最后的输出结果不是期待中的test,而是undefined,说明此时的this指向了window对象<br>
原因就在于类似setInterval这样的函数在调用代码时运行在域所在函数完全分离的执行环境上。<br>
要解决这个问题,可以参考下面的3个方法:<br>
###1.将当前对象的this保存为一个变量,然后定时器内的函数用闭包访问该变量
```javascript
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
```javascript
function getStock(name){
this.name = name;
setInterval(function(){
console.log("name1 is:"+this.name);
}.bind(this),2000);
}
var stock = new getStock("test");
```
当被绑定函数执行时,bind会创建一个新函数,并将第一个参数作为新函数运行的this。<br>
同时,也可以使用apply和call方法,不过要注意call会在调用后立即执行,相当于失去延时效果<br>
###3.箭头函数
```javascript
function getStock(name){
this.name = name;
setInterval(() => {
console.log("name1 is:"+this.name);
},2000);
}
var stock = new getStock("test");
```
ES6标准中的箭头函数的this总是指向词法作用域,也就是外层调用者<br>
参考:<br>
[关于setInterval和setTImeout中的this指向问题](https://www.cnblogs.com/zsqos/p/6188835.html)<br>
## 慕课网课程关于上下文环境的教程
[慕课网课程关于上下文环境的教程(https://www.imooc.com/video/7558)<br>
this的使用:
<br>
*1.作为对象的方法
this在方法内部,this就指向调用这个方法的对象
<br>
```javascript
var pet = {
words = 'miao',
speak : function(){
console.log(this.words);
console.log(this === pet);
}
}
pet.speak();
//miao
//true
```
*2.函数的调用
this指向执行环境中的全局变量(浏览器->window || nodejs ->global)
<br>
```javascript
function pet(words){
this.words = words;
console.log(this.words);
console.log(this);
}
pet('miao');
//miao
//window
```
3.构造函数
this所在的方法被实例对象所调用,那么this就指向这个实例对象
<br>
```javascript
function pet(words){
this.words = words;
this.speak = function(){
consoel.log(this.words);
}
}
var cat = new pet('miao');
cat.speak();
//miao
```
### 继承
```javascript
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的一些零散的笔记
### 判断一个变量是否为数组
常用的方法:<br>
```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]'
```
一般来说,多数主流框架都使用最后一种方法判断。另外要补充,这种方法不仅能判断是否为数组,还能判断是否为字符串等等。<br>
demo:<br>
```javascript
Object.prototype.toString.call(2);
Object.prototype.toString.call(new Object);
```
### split、slice、splice的分辨
* slice: 不会修改原来的数组,截取数组元素返回
<br>
语法:`slice(start,[end])`
<br>
```javascript
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: 直接修改原数组,删除或替换原数组中的指定元素,返回的是被删除或替换的元素组成的数组(注意区分返回值和被操作值)
<br>
```javascript
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: 根据特定的字符切割字符串并且返回生成的数组
<br>
```javascript
str = "Hello!World";
str2 = str.split("!");
console.log(str2); //Hello,World
console.log(str2.length); //2
```
### 数组去重
indexof:<br>
```javascript
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]`
<br>
filter+indexof:<br>
```javascript
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`
<br>
#### ES6的新方法
ES6提供了`Set`数据结构,它类似于数组(具有 iterable 接口),但是其成员是唯一的,利用这个特效可以实现数组去重<br>
```javascript
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`
<br>
ES6还有一种数据结构`Map`,同样可以用来实现:<br>
```javascript
function unique(arr){
const m = new Map();
return arr.filter((a) => !m.has(a) && m.set(a,1))
}
```
对于`Map`结构,如果对同一个键多次赋值,后面的值将覆盖前面的值。<br>
所以,也可以实现`NaN`去重<br>
<br>
参考:<br>
[Github-JavaScript专题之数组去重](https://github.com/mqyqingfeng/Blog/issues/27)<br>
[数组去重](https://wy1009.github.io/2017/03/09/%E6%95%B0%E7%BB%84%E5%8E%BB%E9%87%8D/#more)<br>
# JavaScript随笔
<b>说明:</b>
本篇是JavaScript的一些零散的知识点汇集<br>
## Array.prototype.slice.call(arguments)将对象转换为数组
`Array.prototype.slice.call(arguments)`能将具有length属性的对象转成数组,出来IE的节点集合<br>
首先,`slice([begin],[end])`方法返回一个从开始到结束(不包括结束)选择的数组的一部分浅拷贝到一个新数组对象。
原始数组内容不变<br>
```JavaScript
var arr = ['a','b','c','d'];
console.log(arr.slice(1,3));
//['b','c']
```
`slice` 不修改原数组,只会返回一个浅复制了原数组中的元素的一个新数组。原数组的元素会按照下述规则拷贝:
<ul>
<li>如果该元素是个对象引用 (不是实际的对象),slice 会拷贝这个对象引用到新的数组里。两个对象引用都引用了同一个对象。
如果被引用的对象发生改变,则新的和原来的数组中的这个元素也会发生改变。</li>
<li>对于字符串、数字及布尔值来说(不是 String、Number 或者 Boolean 对象),slice 会拷贝这些值到新的数组里。
在别的数组里修改这些字符串或数字或是布尔值,将不会影响另一个数组。</li>
</ul>
### 将类似数组的对象/集合转换为数组
```javascript
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` 来简化该过程。<br>
```javascript
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]
```
### 通用函数
```javascript
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;
}
}
```
参考:<br>
[MDN - Array.prototype.slice()](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/slice)<br>
[CSDN - Array.prototype.slice.call()方法详解](http://blog.csdn.net/i10630226/article/details/49702375)<br>
# JavaScript中for...of...和for...in...的区别
二者都是迭代的方法,区别在于迭代方式<br>
迭代的概念可以看作遍历<br>
最早数组遍历的方式:<br>
```javascript
var arr = [1,2,3];
for(var i=0;i<arr.length;i++)
console.log(arr[i]);
```
ES5后引入了`forEach`:
```JavaScript
var arr = [1,2,3];
arr.forEach(function(a){
console.log(a);
});
```
虽然简洁了一些,不过还是存在一些缺陷的:<br>
1.不能使用break退出循环<br>
2.不能使用return返回内容到外层<br>
所以我们想到了`for...of`和`for...in`两个语句
<br>
说明:`let...of`和`for...of`等效;`let...in`和`for...in`等效<br>
`for...in`语句以任意顺序遍历一个对象的可枚举属性(表面上循环结果是key)。对于每个不同的属性,语句都会被执行。<br>
```JavaScript
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
```
上述代码正确地将数组内的元素遍历了出来,<b>但是同时将数组的属性也一并打印出来了</b>
因此可以看出,作用于数组的for-in循环除了遍历数组元素以外,还会遍历自定义属性<br>
同时要注意:<br>
<b>for...in是按照随机顺序遍历数组的;假如数组内元素是String,可能会无意间进行字符串运算</b>
<br>
为了解决前面方法的不足,ES6引入了`for...of`<br>
`for...of`语句在可迭代对象(包括 Array,Map,Set,String,TypedArray,arguments 对象等等)上创建一个迭代循环,调用自定义迭代钩子,并为每个不同属性的值执行语句。<br>
```JavaScript
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<br>
```
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`的另外一个对比示例:
```JavaScript
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,而不是任何对象的属性。
*/
```
### 总结一下
<ul>
<li>for...in 语句以原始插入顺序迭代对象的可枚举属性。
for...of 语句遍历可迭代对象定义要迭代的数据。</li>
<li>for...in循环出的是key,for...of循环出的是value</li>
<li>for...of不能循环普通的对象,需要通过和Object.keys()搭配使用。此时用for...in就很好</li>
</ul>
参考:<br>
[MDN - for...of语句](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/for...of)<br>
[MDN - for...in语句](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/for...in)<br>
[SegmentFault - javascript总for of和for in的区别?](https://segmentfault.com/q/1010000006658882)<br>
[Javascript中的for-of循环](https://github.com/wujunchuan/wujunchuan.github.io/issues/11)<br>
# JavaScript字符串截取函数slice、substring、substr
## substring
功能:返回一个索引和另外一个索引之间的字符串<br>
语法:`str.substring(start,[end])`<br>
<br>
注意:
<ul>
<li>截取范围包括start但是不包括end</li>
<li>若start==end,则返回空</li>
<li>end可省略,如果省略则表示从start截取到字符串末尾</li>
<li>若某个参数是NaN或者小于0,那么等效为0;若某个参数大于字符串长,那么等效为字符串长</li>
<li>若start大于end,那么start和end交换,比如str.substring(2,0) == str.substring(0,2)</li>
</ul>
<br>
demo:<br>
```javascript
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'
```
<br>
## substr
功能:返回从指定位置开始的字符串中指定字符数的字符串<br>
语法:`str.substr(start,[length])`<br>
<br>
注意:
<ul>
<li>它会从start位置截取长度为length的字符串</li>
<li>若start是正数且大于或等于字符串长度,则返回一个空字符串</li>
<li>若start为负数,则将该值加上字符串长度再进行计算,若还是负数,则从0开始截取(实际上就是倒过来从尾部开始截取)</li>
<li>若length为0或负数,则返回空;若length省略,则截取到末尾</li>
</ul>
<br>
demo:<br>
```javascript
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
功能:返回一个索引到另一个索引间的字符串<br>
语法:`str.slice(start,[end])`<br>
<br>
注意:
<ul>
<li>start和end都为正数时,截取范围不包括start和end</li>
<li>若start为负数,start=start+length,若还是负数,则从0开始截取(实际上就是倒过来从尾部开始截取)</li>
<li>若start大于或等于字符串长,则返回空</li>
<li>若end省略,则截取到字符串末尾;若end为负数,则end=length+end</li>
</ul>
<br>
demo:<br>
```javascript
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): '
```
补充:<br>
### 注意和split的区分
split是将一个字符串分割为字符串数组<br>
demo:<br>
```javascript
"hello".split("") //可返回 ["h", "e", "l", "l", "o"]
"2:3:4:5".split(":") //将返回["2", "3", "4", "5"]
```
<br>
参考:<br>
[掘金-JS字符串截取函数slice(),substring(),substr()的区别](https://juejin.im/post/59e2af3151882578cf573319)<br>
以上是关于markdown JavaScript的随笔的主要内容,如果未能解决你的问题,请参考以下文章