【进阶】隐式转换

作者: woow_wu7 | 来源:发表于2019-03-19 22:09 被阅读0次

目录:
getBoundingClientRect
IntersectionObserver
traget 和 currentTarget
canvas
图形验证码

(一) 隐式转换

强制转换
主要指 Number(),String(),Boolean()三个函数

  • 案例
[] == ![]   ---------------- true


(1) !优先级高于 == , 先算![] 是false, 除了undefined,null,NaN,+0,-0,''外都是false
(2) 变为 [] == false, == 运算符会把会把运算子转成 (数值),除了+,其他运算符都转成数值计算
(3) 变为 Number([]) == Number(false)
(4) 变为 Number([]) == 0
(5) Number([])先调用valueOf([])->返回数组本身,是一个对象 -> 再调用toString([]) -> 返回空字符转,是原始类型 -> Number('') -> 0
(6) 0 == 0 变成 0 === 0
(7) 结果返回 true
[] + []  ----------------------> ''


(1) 运算子是对象,先转成原始类型
(1) valueof([]) + valueof([]) =========> [] + []
(2) toString([]) + toString([]) =======> ''+'' 
(3) '' + '' 最后得到 ''
valueOf() 和 toString()

------
valueOf()是所有对象都拥有的方法,表示对改对象求值,不同对象的valueOf表现不一致
------


数组
valueOf()返回数组本身
toString()返回数组的字符串形式
[1,2,3,[4,5]].toString()  --------------- '1,2,3,4,5'

对象
valueOf()默认情况下返回对象本身
toString()返回对象的字符串形式
{a:1}.toString() ------------------------- '[object, object]'

数字
toStrig() Number对象toString()用来将一个数值转换成字符串
toString(进制)
(10).toString() -------------- 10 一定要放在括号里,不然会被当作小数点处理  // '10'


字符串
字符串的valueOf() ---------------- 返回字符串本身
其实: 原始类型的值的valueOf() 都是返回原始数据本身

(1) Number()强制转换 ---- 返回数字或NaN

参数:原始类型 和 对象 两种情况
结果:任何数据类型转换成数字,要么是 数字,要么是 NaN

一:参数是原始类型

1. 为0的情况
Number(false) -------------- 0
Number(null) --------------- 0
Number('') ----------------- 0

2.为NaN的情况
Number('123abc') --------- 不能被解析成数值的字符串 ----------- NaN
Number(undefined) ------------------------------------------- NaN

3.
Number('123') ---- 能解析成数值的字符串转成对应的数值
Number(true) ---- 1


二:参数是对象

1. 单个数值的数组
Number([1]) ---------------- 1

2. 当参数为对象,除了单个数值的数组外都是NaN
当Number()的参数是对象时,转换规则


总结:
先调用valueOf() -> 对象 -> toString() -> 对象 -> 报错
以上步骤,如果返回原始类型的值,就调用Number(),并中止后续步骤



1. 第一步调用自身的 valueOf 方法,如果返回原始类型的值,则直接对该值使用 Number()函数,不再进行后续步骤
2. 第二步如果valueOf()返回的仍然是对象,则调用对象自身的 toString()
         如果toString()返回原始类型的值,则对该值使用Number()函数,不再进行后续步骤
3. 第三步如果 toString() 方法返回的是对象,就报错

(2) String()强制转换

参数:原始类型的值 或 对象

参数是原始类型的值:

1. 字符串 ------ 返回字符串
2. 数值 -------- 返回相应的字符串
3. 布尔值 
  - true -------- 返回 'true'
  - false ------- 返回'false'
4. undefined ---- 返回 'undefined'
5. null --------- 返回 'null'


参数是对象
String() 参数是对象 ---------------- 返回类型字符串

参数是数组
String() 参数是数组 ---------------- 返回数组的字符串形式


String({a: 1}) --- 先调用toString(),返回"[object Object]" ,发现是字符串,则调用String(),然后中止
String([1, 2, 3]) // "1,2,3"
当 String() 的参数是对象时的转换规则:


总结:
先调用 toString() -> 对象 -> valueOf() -> 对象 -> 报错
以上步骤,如果返回原始类型的值,则调用 String(),并中止后续步骤



1. 第一步调用对象的toString()方法,返回原始类型的值,则对该值使用String() 函数,不再进行以下步骤
2. 第二步如果toString()方法返回的是对象,则再调用对象的valueOf()方法,
               如果valueOf()方法返回原始类型的值,则对该值使用String()方法,不再进行以下步骤
3. 如果valueOf()返回对象 , 就报错

(3) Boolean()

Boolean() 可以将任意类型的值,转换成布尔值

除了以下6个值是false, 其他都是true


为false的情况:
'', null, undefined, +0, -0,NaN

(二) 自动转换

自动转换的规则:预期是什么类型的值,就调用改类型的转换函数,如果该位置既可以是字符串,也可以是数值,则默认改为数值

(1) 不同类型的数据相互运算

(2) 对非布尔类型的数据求布尔值

(3) 对非数值类型的值,使用一元运算符

自动转换成字符串

预期为字符串的地方,会将非字符串的值转换成字符串

  • 具体规则:先将复合类型的值转换成原始类型的值,再将原始类型的值转成字符串
  • 字符串自动转换:主要出现在字符串(加法运算时),当一个值是字符串,另一个值是非字符串,后这转换成字符串

自动转换成数值

  • 除了+加法运算可能转成字符串,其他运算符都会把运算子转换成数值









运算符

(1) 加法运算符

重载:运算子的不同,导致不同的语法行为,这种现象叫做 重载
加法运算符是在运行时决定:相加链接
注意:除了加法运算符,其他运算符都不会发生重载,他们的规则是:将所有运算子转换为数值,再进行相应的数值运算

  • 对象相加:如果运算子是对象,必须转成原始类型的值再相加
对象转成原始类型的值规则:

1. 第一步调用对象的 valueOf() 方法,默认对象的valueOf()返回自身
2. 第二步调用对象的 toString()方法:
  数组的toString()返回数组的字符串形式, [].toString()是''   [1,2,[3]].toString()是'1,2,3'
  对象的toString()返回类型字符串,{a: ''}是'[object, object]'

(2) 取反运算符

  • 对于非布尔值,取反运算符会将其转化为布尔值
  • 除了null, undefined, NaN, +0, -0, ''这6个字符取反是true,其他的所有字符取反操作都是false









Math.randow()

生成 0-1 之间的随机数,包括0,不包括1

生成任意范围内的随机数
Arbitrary: 任意的

function getRandomArbitrary(min, max) {
  return Math.random() * (max - min) + min;
}

Canvas

1. ctx.fillText(text, x, y [, maxWidth]) ------- 在指定的(x,y)位置填充指定的 ( 文本 )
2. ctx.strokeText(text, x, y [, maxWidth]) ----- 在指定的(x,y)位置绘制 ( 文本边框 )
3. ctx.font = 'bold 12px arial' ---------------- 指定当画布上文本内容的字体属性
4. ctx.fillStyle=color|gradient|pattern; ------- 设置或返回用于填充绘画的颜色、渐变或模式



canvas清除画布的2种方法:
1. ctx.clearRect(x,y,width,height);
  - x,y 是矩形左上角的坐标
  - width, height是需要清除的矩形的宽度和高度
2. 重新设置canvas的宽高  canvasDom.width = canvasDom.width;




将canvas转换成图片
canvasDom.toDataURL([type, encoderOptions]);
- type: 指定图片的类型,可选,默认值是image/png
- encoderOptions: 在指定为 image/jpeg 或 image/webp下,可以从 0 到 1 的区间内选择图片的质量
- 返回值: 包含 data-URI 的 DOMString
     document.getElementById('canvas').onclick = function() {
        ctx.clearRect(0,0,150,50);// canvasDom.width = canvasDom.width;
        draw();
        const resImg = canvasDom.toDataURL('image/png');
        console.log(resImg);
        const imgx = new Image();
        imgx.src = resImg;
        document.body.appendChild(imgx);
      }


  

<!DOCTYPE html>
<html>
<head>
  <style>
  </style>
</head>
<body>
  <div class="a">
    <canvas
      id="canvas"
      width="150"
      height="50"
      style="display: block; border: 1px solid red"
    ></canvas>
  </div>
  <script>
    function draw() {
      const canvasDom = document.getElementById('canvas');
      const ctx = canvasDom.getContext('2d');
      const code = 'ABCDEFGHIJKLMNOPQRSTUVWZYZabcdefghijklmnopqrstuvwxyz0123456789'.split('');
      const color = `rgb(${new Array(3).fill(0).map((_, index) => Math.floor(Math.random()*255)).join()})`;
      new Array(4).fill(0).forEach((_, i) => {
        const x = 30 + 20 * i;
        const y = 30 + Math.random()*5 * i;
        const text = code[Math.floor(Math.random()*36)];
        ctx.font = "bold 20px 微软雅黑";
        ctx.fillStyle=color;
        ctx.fillText(text, x, y);
      })
      // 制作线
      for (var i = 0; i < 8; i++) {
          ctx.beginPath();//声明开始一个路径
          ctx.moveTo(Math.random() * 150, Math.random() * 50);//起点
          ctx.lineTo(Math.random() * 150, Math.random() * 50);//终点
          ctx.strokeStyle = color;//颜色
          ctx.stroke();//制作绘制
      }
      // 制作点
      for (var i = 0; i < 50; i++) {
            ctx.beginPath();//声明开始一个路径
            var x = Math.random() * 149;
            var y = Math.random() * 49;
            ctx.moveTo(x, y);
            ctx.lineTo(x + 1, y + 1);
            ctx.strokeStyle = color;//颜色
            ctx.stroke();//制作绘制
      }
    }
    window.onload = function() {
      draw();
      document.getElementById('canvas').onclick = function() {
        draw();
      }
    }
  </script>
</body>
</html>

https://www.kancloud.cn/juan1996/javascript/823732
https://www.cnblogs.com/fangsmile/p/7171789.html?utm_source=debugrun&utm_medium=referral重设宽高
https://www.jianshu.com/p/17d7e5ddf10a










getBoundingClientRect

getBoundingClientRect用于获取某个元素相对于视窗的位置集合

const rectObject = object.getBoundingClientRect();
( rectangle: 是矩形的意思 )

1. 返回值:
TextRectangle对象,每个矩形具有四个整数性质( 上,右,下,左 )表示的坐标的矩形,以像素为单位。

rectObject.top:元素上边到视窗上边的距离;
rectObject.right:元素右边到视窗左边的距离;
rectObject.bottom:元素下边到视窗上边的距离;
rectObject.left:元素左边到视窗左边的距离;

IntersectionObserver 懒加载,数字滚动

var io = new IntersectionObserver(callback, options)
// intersection 是交叉的意思


(1) IntersectionObserver构造函数接受两个参数
 - callback : 被监听元素的可见性变化时,触发的回调函数,回调函数的参数是一个数组
 - options :配置参数对象,可选



(2) callback
- callback回调函数会触发两次,目标元素进入视口和离开视口时都会触发
- callback(([...]) => {...})回调函数的参数是一个数组,成员是IntersectionObserverEntry对象
-
- IntersectionObserverEntry对象 提供目标元素的信息,一共7个属性
  其中最重要的几个:
  1. ( isIntersecting ) 和 ( intersectionRatio ) 用来判断元素是否可见
    isIntersecting : 目标元素是否可见 boolean ,可见为true
    intersectionRatio : 目标元素的可见比例,即intersectionRect占boundingClientRect的比例
    // ratio 是比例的意思
    var io = new IntersectionObserver(
      entries => {   
        console.log(entries);
      }
    );
  2. boundingClientRect ----- 目标元素相对于浏览器视窗四个点坐标对象
  3. intersectionRect   ----- 目标元素与根元素的交叉区域信息
  4. target             ----- 被观察的目标元素的DOM节点(重要,用于懒加载!!!)




(3) options
- root :用于观察的根元素,默认是浏览器的视口,也可以指定具体元素,
         指定元素的时候用于观察的元素必须是指定元素的子元素
- threshold :用来指定交叉比例,决定什么时候触发回调函数,是一个数组,默认是[0]。
- rootMargin :用来扩大或者缩小视窗的的大小
// threshold 是阈值,临界值的意思
const options = {
    root: document.querySelector('.box'),    --------- 设置被观察元素的根元素
    threshold: [0, 0.5, 1],                  --------- 交叉比例,什么时候触发
    rootMargin: '30px 100px 20px'            --------- 扩大或缩小视窗
}



(4) 构造函数返回值
IntersectionObserver构造函数返回值,是一个观察器实例,有如何属性和方法:
// 开始观察
io.observe(document.getElementById('example'));    // observe方法的参数是DOM节点
// 停止观察
io.unobserve(element);
// 关闭观察器
io.disconnect();



实例:
<template>
  <div class="intersection" ref="inter-wrap">
    <div class="inter-top">目标红块相对于浏览器:{{this.observeDomWinowTop}}px</div>
    <div class="inter-top2">目标红块相对于根(在options中设置的):{{this.observeDomRootTop}}px</div>
    <div class="inter-inner" ref="inter-inner"></div>
    <div><br><br><br><br><br><br>11</div>
    <div><br><br><br><br><br><br>11</div>
    <div><br><br><br><br><br><br>11</div>
    <div><br><br><br><br><br><br>11</div>
    <div><br><br><br><br><br><br>11</div>
  </div>
</template>

<script>
export default {
  name: "intersection",
  data() {
    return {
      observeDomWinowTop: 0
    }
  },
  mounted() {
    console.log('1')
    const callback = (entry) => {
      console.log(entry)
      this.observeDomWinowTop = entry[0].boundingClientRect.top;
      this.observeDomRootTop = entry[0].intersectionRect.top;
    };
    const options = {
      root: this.$refs['inter-wrap'],
      rootMargin: '10px',
      threshold: [0, 0.5, 1]
    }
    const io = new IntersectionObserver(callback, options);
    io.observe(this.$refs['inter-inner']);
  },
  destroyed() {
    // this.ob.disconnect();
  },
  computed: {
  },
  methods: {
  }
};
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.intersection {
  background: rgb(255, 210, 210);
  width: 300px;
  height: 300px;
  overflow: auto;
}
.inter-top {
  position: fixed;
  top: 100px;
  left: 10px;
  height: 20px;
  width: 300px;
  background: silver;
  color: greenyellow;
}
.inter-top2 {
  position: fixed;
  top: 120px;
  left: 10px;
  height: 20px;
  width: 300px;
  background: silver;
  color: rgb(255, 47, 203);
}
.inter-inner {
  width: 50px;
  height: 100px;
  background: red;
}
</style>

数字滚动:

<template>
  <div class="reel">
    <div class="goMove"  ref="reel">
      <div class="number" v-for="item in numbers" :key="item">
        {{item}}
      </div>
    </div>
    <div>{{left}}</div>
    <br>
    <hr>
    <div class="intersection"></div>
  </div>
</template>

<script>
export default {
  name: "reel",
  data() {
    return {
      numbers: 12,
      left: 1,
    }
  },
  mounted() {
    const reef = this.$refs.reel;
    this.ob = new IntersectionObserver(([entry]) => {
      if (entry.isIntersecting) {
        reef.style.transform = `translateY(-${(this.numbers - 1 ) * 40}px)`
      }
    });
    this.ob.observe(this.$el);
    // 滚动的位置
    this.left = reef.getBoundingClientRect().left;
    console.log(this.left)
  },
  destroyed() {
    // this.ob.disconnect();
  },
  computed: {
    getNumbers() {
      return String(this.numbers).split()
    }
  },
  methods: {
  }
};
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.reel {
  height: 40px;
  width: 40px;
  line-height: 40px;
  overflow: hidden;
  margin: 0 auto;
  background: yellow;
  user-select: none;
}
.number {
  height: 40px;
  width: 40px;
}
.goMove {
  transition: all .5s;
}
.intersection {
  background: rgb(255, 210, 210);
  width: 600px;
  height: 600px;
  display: block;
}
</style>

http://www.ruanyifeng.com/blog/2016/11/intersectionobserver_api.html
https://www.w3cplus.com/vue/build-an-infinite-scroll-component-using-intersection-observer-api.html
https://www.jianshu.com/p/84a86e41eb2b
http://www.zhangyunling.com/811.html










2019-3-15
复习
事件模型

  • EventTarget接口
addEventListener绑定事件的监听函数
removeEventListener移除事件的监听函数
dispatchEvent触发事件
  • EventTarget.addEventListener(type, listener[, useCapture])
EventTarget.addEventListener()用于在当前节点或者对象上,定义一个特定的事件监听函数
一旦这个事件发生,就会执行监听函数,没有返回值


EventTarget.addEventListener(type, listener[, useCapture])

type: 事件名称
listener: 事件发生时,执行的监听函数 ----- 除了监听函数,也可以是有 handleEvent 方法的对象
userCapture: 是否在捕获阶段触发 ---- 默认是false, 也可以是配置对象

例如:
const customObj = {
  handleEvent: function(e){ ... }   ------------ 具有handleEvent方法的对象
};
const optionsObj = {
  capture: false,  ------------- 是否在捕获阶段触发
  once: false, ----------------- 是否监听函数只触发一次,然后自动移除
  passive: true ---------------- 监听函数不会调用事件的 preventDefault 方法 ( passive是被动的意思 )
};
document.body.addEventListener('click',  customObj, optionsObj)



重点:
1. addEventListener可以为同一个对象的同一事件,添加不同的监听函数,先添加先触发
2. 为同一个对象的同一个事件,添加同一个监听函数,则只会执行一个,多余的自动被移除
3. 如果希望向监听函数传递参数,可以用匿名函数包装监听函数
4. 监听函数内部 this => 指向的是 ( 绑定事件的节点对象 ) !!!!!!!!!!

function print(x) {
  console.log(x);
}
var el = document.getElementById('div1');
el.addEventListener('click', function () { 
  print('Hello');  ---------------- 传参
  console.log(this)   ---------- 监听函数中的this,指向的是绑定事件的节点对象,即el
}, false);
  • EventTarget.removeEventListener(type, listener[, useCapture])
参数和 addEventListener()方法的参数完全一致

注意: removeEventListener()方法移除的监听函数,
要和addEventListener()的监听函数一致,并且,要是同一个节点。

`即同一节点元素,同一个监听函数,三个参数要完全一致`
  • EventTarget.dispatchEvent( event ) ------- 触发事件
    描述:在当前节点触发指定事件,(触发事件后,从而执行事件的监听函数)
    参数:event 是 Event 对象的实例
    返回值: 返回布尔值
    只要有一个一个监听函数调用了e.preventDefault就返回false, 否则返回true
para.addEventListener('click', hello, false);
var event = new Event('click');  ---------- 生成click事件的实例
para.dispatchEvent(event); ---------------- 直接触发事件,从而触发hello监听函数

注意:
1. 这样会触发click事件,而不是手动触发
2. disPatchEvent()的参数为空,或者不是一个有效的事件对象将报错


var canceled = !cb.dispatchEvent(event); 
if (canceled) {  ------------------ 为true,则调用了e.preventDefault,dispatchEvent()返回值是false
  console.log('事件取消');
} else {
  console.log('事件未取消');
}

监听函数
事件绑定监听函数的三种方法:

  • HTML的on-属性
    事件监听属性:元素节点的 ( 事件监听属性 ) 都是 on + 事件名
    属性的值:是将要执行的代码,而不是函数
    触发:只在冒泡阶段触发
    直接设置on-属性,与通过元素节点的setAttribute方法设置on-属性,效果是一样的
<!-- 正确 -->
<body onload="doSomething()">   ---------- 执行函数
等同于
document.body.setAttribute('onload', 'doSomething()')

<!-- 错误 -->
<body onload="doSomething">
  • 元素节点的事件属性

注意:

  1. 元素节点的事件属性的值是事件名
  2. HTML中on+属性的值是 将要执行的代码
事件绑定监听函数的三种方法比较:

1. HTML中 on-属性:违背HTML和JS分离的原则
2. 事件属性:事件只能绑定一个监听函数,后面的会覆盖前面的
3. addEventListener:
 - 同一事件,可以绑定多个监听函数
 - 可以指定事件在哪个阶段触发,触发几次
 - 除了DOM中有,在window, XMLHttpRequest也有该接口,统一了js监听函数接口

target 和 currentTarget

  • traget指:触发事件的节点
  • currentTarget指:绑定了事件监听函数的节点,和监听函数中的 this 保持一致

event对象

  • 事件发生后,会产生一个event对象,作为参数传给监听函数
  • 所有的事件,都是Event对象的实例,或者说继承了Event.prototype对象
event = new Event(type, options);

type: 事件名称,是一个字符串

options: 配置对象,有两个属性
1. bubbles:布尔值,可选,默认为false,表示事件对象是否冒泡。
2. cancelable:布尔值,可选,默认为false,表示事件是否可以被取消
 即能否用Event.preventDefault()取消这个事件。
 一旦事件被取消,就好像从来没有发生过,不会触发浏览器对该事件的默认行为。

函数

  • 函数也是个值,凡是使用值的地方都能使用函数,比如:可以把函数赋值给变量或者对象的属性,也可以把函数作为参数传入另一个函数,或者作为函数的返回值。
  • 函数声明的三种方法\
1. function a() {}  ------------------- function关键字
2. cons a = function() {}; ------------- 函数表达式,注意函数表达式所在的赋值语句的末尾要加上分号,表示语句的结束
3. 构造函数,不使用



注意:
const b = function a() {.......}
采用函数表达式方式声明函数,通常是一个匿名函数,但是上的面写法使用了函数名a
- 函数名a只在函数内部有效
- 这样做的好处:可以在函数内部调用自己,和方便拍错



var print = function x(){
  console.log(typeof x);  -------------- 函数名 x 只在函数内部有效,在函数外打印,会报错 not defined
};
x
// ReferenceError: x is not defined
print()
// function
  • 函数重复声明时,后面的声明会覆盖前面的声明
  • 函数又成为第一等公民
  • ( 函数名 ) 的提升:
    js将 ( 函数名 ) 视同 ( 变量名 ),function命令声明函数时,会像声明变量一样,被 提升到代码块头部、
f();   --------------------- 先调用不会报错,因为 function 关键字声明的函数存在变量提升,提升到代码头部
function f() {}  ----------- 变量提升




注意:采用函数表达式声明的函数(即变量赋值的方式声明函数),不存在变量提升

f();
var f = function (){};  // TypeError: undefined is not a function

上面的代码等同于下面的形式:

var f;
f();   --------------------- 调用 f 时,f只是被声明了,但是还没有被赋值,
f = function () {};
  • 如果同时采用赋值语句声明和function关键字声明函数,js则总是采用赋值运算符声明的函数
  • 不能在条件语句中声明函数( 如 if ,try ),--- 因为存在变量提升\

函数的属性和方法

  • 函数的name属性,返回属性的名称。用处是:参数是一个函数时,获得参数函数的名字
var f3 = function myName() {};   ------ 注意:赋值语句声明具名函数时,name属性,返回的是function后面的函数名
f3.name // 'myName'
  • 函数的length属性,返回函数定义时,参数个数
    对比:arguments属性获得实参组成的数组
  • toString返回函数源码的字符串形式

函数作用域

  • 作用域:变量存在的范围
  • es5中有两种作用域:
    全局作用域:变量在整个程序中一直存在,所有地方都可以读取
    函数作用域:只在函数内部存在,函数内部可以访问,外部无法访问
  • es6中增加了块级作用域
  • 函数外部声明的变量是全局变量,函数内部可以读取
  • 函数内部声明的变量是局部变量,函数外部无法读取
<script>
  var c = 1;     ----------------------- 函数外部声明的变量是全局变量,函数内部可以读取
  const a = function() {
    var b = 10; ------------------------ 函数内部声明的变量是局部变量,函数外部无法读取
    console.log(c) ----- 1
  };
  a();
  console.log(b) ------- 报错
</script>
  • 函数内部定义的变量,会在该作用域内部,覆盖同名的全局变量
var c = 1;
const a = function() {
  var c = 11;  ------------------------- 函数内部定义的变量,会覆盖同名的全局变量
  console.log(c) ----- 11 而不是 1
};
a();
c ----- 1
  • var 命令,局部变量只能在函数中声明,在其他区块中声明,一律是全局变量
if (true) {
  var x = 5;
}
console.log(x);  // 5 ------------------ 变量提升,函数中会提升带函数作用域(局部)顶部

函数本身的作用域

  • 函数本身也是一个值,也有自己的作用域
  • 函数的作用域,是函数声明时所在的作用域,与函数运行时所在的作用域无关
  • 总之:函数运行时的作用域,是定义时的作用域,而不是调用时所在的作用域
var a = 10;
var b = function() {
  console.log(a);
};
function x() {
  var a = 20;
  b();       ------------------- 函数的作用域是函数声明时所在的作用域,与调用时的作用域无关。所以输出 10 而不是 20
}
x(); // 10
  • 容易犯错的点:如果函数b调用了函数a,却没有考虑到函数a不会引用函数b中的变量!!!!
var a = function() {
  console.log(x)
 };
 var b = function() {
  var x = 10;
  a();   ------------------ 函数运行时所在的作用域,是函数定义时所在的作用域,而不是调用时所在的作用域
 };
 b(); // 报错 ---------- 因为函数b调用函数a,但是a在b外部定义,作用域在外部,所以不会引用b内部的变量
  • 函数内部声明的函数, 作用域绑定在函数内部
function foo() {
  var x = 1;
  function bar() {   ------------- 函数内部声明的函数,作用域绑定在函数内部,所以输出1
    console.log(x);
  }
  return bar;
}

var x = 2;
var f = foo();
f() // 1

函数的参数

函数参数的传递方式

  • 函数的参数是原始类型的值,传递方式为 (传值传递),在函数内部修改参数值,不会影响到函数外部
  • 函数的参数是复合类型的值,传递方式为(传址传递),在函数内部修改参数值,会影响到函数外部
  • 注意:如果函数内部修改的不是参数对象的某个属性,而是替换掉整个参数,则不会影响原始值
    <script>
        var str = 'woow';
        var num = 10;
        var boo = true;
        var arr = [1,2];
        var obj = {name: 'wang'};
        var fn = function(){ console.log('2222')};

        var x = function(str, num, boo, arr, obj, fn) {
            str = 'wu';
            num =  20;
            boo = false;  ----------- 数字,字符串,布尔值是传值传递,函数内修改参数值,不会影响外部,是值的拷贝
            arr[0] = 111;
            obj.name = 'zhang';
            fn = function() { console.log('1111')}; ----- 数组,对象,函数是传址传递,函数内修改参数,会影响函数外部,地址的拷贝
        }
        x(str, num, boo, arr, obj, fn);
        console.log(str, num, boo, arr, obj, fn)
    </script>

执行结果
woow 10 true
[111, 2]
{name: "zhang"}
function (){ console.log('2222')}

-----




注意:如果函数内部修改的不是参数对象的某个属性,而是替换掉整个参数,则不会影响原始值
因为:整体赋值后,变量指向的不是原来的地址,对应的就是不同的值
var obj = [1, 2, 3];
function f(o) {
  o = [2, 3, 4];  -------- o指向的已经不是obj的地址了,是不同的地址,对应不同的值
}
f(obj); // [1, 2, 3]
    <script>
        var arr = [1,2];
        function mm() {
            console.log(arr, 'arr')
        }
        var x = function(arr) {
            var arr == [1,2,3];
            arr[0] = 111;
            mm();  -------------- 函数运行时的作用域,是函数定义时的作用域,与函数调用时的作用域无关,但是在内部被修改了
        }
        x(arr);   -------------- 运行的值是 [111, 2]
    </script>

同名参数

  • 如果有同名参数,则取后面的值
function f(a, a) {
  console.log(a);
}
f(1, 2) // 2


---------------------------


function f(a, a) {
  console.log(a);
}
f(1) // undefined

arguments对象

  • arguments对象只有在函数内部才可以使用
  • arguments.length是实参的个数
  • arguments是类似数组的对象,不是真正的数组
  • 正常模式下,arguments对象可以在运行时修改,严格模式下arguments对象只读,不能被修改
    <script>
        var x = function(a,b) {
            const x = Array.prototype.slice.call(arguments); ---------- 将类似数组的对象转成真正的数组,也可以用Array.from()
            console.log(Array.isArray(x));    -------------------------  数组/true
            console.log(Array.isArray(arguments)); -------------------- 类似数组的对象/false
        }
        x(1,2);
    </script>

闭包

  • 函数作用域: 函数外部无法读取函数内部的变量
  • 链式作用域:子对象会一级一级向上寻找父对象的变量,反之则不成立。即所有父对象的变量对子对象可见,反过来不成立
  • 闭包的作用:
  1. 读取函数内部的变量
  2. 让这些变量始终保存在内存中,即闭包可以记住诞生环境一直存在
  3. 封装对象的私有属性和私有方法
function f1() {
  var n = 999;
  function f2() {  ----------- 定义在函数内部的函数,作为外层函数的返回值,读取函数内部变量
    console.log(n);
  }
  return f2;
}

var result = f1();
result(); // 999
闭包-------- 封装私有属性和方法

  <script>
    window.onload = function() {
      const Persion = function(name) {
        let age;
        function setAge(a) {
          age = a;
        };
        function getAge() {
          return age
        };
        return {
          name,
          getAge,
          setAge,
        }
      };
      const p1 = Persion('wang');
      p1.setAge(20);
      console.log(p1.getAge())
    };
  </script>


注意:
外层函数每次运行就会生成一个新的闭包,而这个闭包又会保存外层函数的内部变量,所以内存消耗巨大
不要滥用闭包,否则会造成性能问题

立即调用的函数表达式

  • function出现在句首,表示语句(不表示表达式),js认为是函数定义,不应该以() 结尾
  • 解决办法是不然function出现在行首
  • 对匿名函数使用立即调用的函数表达式目的主要有两个:
  1. 不必为函数命名,避免污染全局变量
  2. 二是 IIFE 内部形成了一个单独的作用域,可以封装一些外部无法读取的私有变量
(function(){ /* code */ }());
// 或者
(function(){ /* code */ })();









2018/4/3
复习

对象
  - valueOf()返回对象本身
  - toString()返回类型字符串 '[object Object]'

数组
  - valueOf()返回数组本身
  - toString()返回数组的字符串形式

函数
  - valueOf()返回函数本身
  - toString()返回函数源码字符串

日期:
  - valueOf() 返回实例对象距离时间0点的毫秒数(=getTime方法)
  - toString()返回日期字符串

总结:
  - valueOf() 返回对象本身,Date对象返回距离时间零点的毫秒数字符串除外
  - toString() 除了Object类型对象返回'[object Type]'字符串外,其他类型对象返回对应的字符串形式

判断数组的三种方法:
  - Object.prototype.toString.call() 或者 Object.prototype.toString.apply()
  - Array.isArray()
  - 实例对象 instanceof 构造函数
  ----- Array.isArray优于 instanceof,因为 Array.isArray可以检测出 iframes


应用:
  - Object.prototype.toString.apply([]) ------------------- 返回 '[object Array]'
  - Object.prototype.toString.apply({}) ------------------- 返回 '[object Object]'
  - Object.prototype.toString.apply(function(){}) --------- 返回 '[object Function]'
  - Object.prototype.toString.apply(undefined) ------------ 返回 '[object Undefined]'
  - Object.prototype.toString.apply(null) ----------------- 返回 '[object Null]'


案例:
const a = [1,2,3,[4,5]];
const b = {}
a.valueOf() // [1,2,3,[4,5]]  ----------------------- 数组的valueOf()返回数组本身
b.valueOf() // {} -------------------------------- 对象的valueOf()返回对象本身
a.toString() // "1,2,3,4,5" ------------------------- 数组的toString()返回数组的字符串形式
b.toString() // '[object object]' ---------------- 对象的toString()返回类型字符串

https://blog.csdn.net/zrn1812083198/article/details/88102569

Number()

Number() ----- 返回 ( 数值 ) 或者 ( NaN )


1. 参数是原始类型的值 
Number(false) ------------------- 0
Number('') ---------------------- 0
Number(null) -------------------- 0
Number(undefined) --------------- NaN
Number('123 adb') --------------- NaN  // parseInt('123 adb')--123,省略第二个参数后者是0,是10进制


2. 参数是对象
参数是对象时,返回NaN
// 特例:成员是单个数值的数组返回数值      Number([1]) ----- 1


3.转换规则
1. 调用ValueOf(),返回原始类型,则Number();返回对象,下一步
2. 调用toString(),返回原始类型,则Number();返回对象,下一步
3. toSting()返回的是对象,报错


4.例子
Number([1,2,3]) --> valueOf() --> [1,2,3] --> toString() --> '1,2,3' --> Number() --> NaN

String()

String()


1.参数是原始类型的值
String(123) ------------------- '123'
String(null) ------------------ 'null'
String(undefined) ------------- 'undefined'


2.参数是对象
1. 对象:返回类型字符串
2. 数组:返回数组的字符串形式


3.转换规则
1. 调用toString(),返回原始值,则String();返回对象,下一步
2.调用valueOf(),返回原始值,则String();返回对象,下一步
2. valueOf(),返回对象,报错


例子:

String({a: 1}) --> toString() --> '[object Object]' --> String() --> '[object Object]'
String([1,3]) --> toString() --> '1,3' --> String() --> '1,3'

触发隐式转换的地方?

什么时候会触发隐式转换?


1. 不同类型的数据相互运算
2. 对非布尔类型的数据求布尔值
3. 对非数值类型的值使用一元运算符 + -

加法运算符

1. 加法运算符:( 相加 ) 或者 ( 链接 )

- 如果一个运算子是字符串,另一个是非字符串,这时非字符串会转换成字符串,再链接在一起
- 计算时一定要小心:
  - 2+3+'5' = '55'
  - 2+'3'+5 = '235'
- 对象的相加
  - 如果运算子是对象,必须先转成原始类型的值,然后再相加
  - 对象转成原始类型的值,计算过程 valueOf --> toString
  - 特例:运算子是 Date对象的实例,则会优先调用 toString

var obj = new Date();
obj.valueOf = function () { return 1 };
obj.toString = function () { return 'hello' };
obj + 2 // "hello2"



2. 重载:运算子的不同,导致了不同的语法行为,这种现象称为重载

3. 除了加法运算符,其他的运算符都不会发生重载,其他所有的运算符都是转成数值在进行数学运算

比较运算符

1. 任何值 与 NaN 比较,返回的都是false

1 > NaN // false
1 <= NaN // false
'1' > NaN // false
'1' <= NaN // false
NaN > NaN // false
NaN <= NaN // false

相关文章

  • 【进阶】隐式转换

    目录:getBoundingClientRectIntersectionObservertraget 和 curr...

  • C++类型转换

    C++的类型转换分为隐式转换和显式转换 隐式转换举例: int i=4; double d=i;//隐式转换 显式...

  • scala-隐式机制及Akka

    隐式机制及Akka 隐式转换 隐式转换和隐式参数时Scala中两个非常强大的功能,利用隐式转换和隐式参数,可以提供...

  • Scala基础——隐式转换

    隐式转换 Scala的隐式转换,其实最核心的就是定义隐式转换函数,即implicitconversion func...

  • 【Scala】Scala 隐式转换 implicit

    本篇结构: 前言 隐式转换类型 隐式转换的规则 -- 如何寻找隐式转换方法 参考博文 一、Implicit 简介 ...

  • Scala 隐式转换

    一、隐式转换 隐式转换需要执行隐式函数,隐式函数是以 implicit 关键字声明的带有单个参数的函数。隐式函数会...

  • 数据类型转换

    写在前面 C/C++编程中常见数据类型转换,包括隐式类型转换和显式类型转换。 1. 隐式类型转换 隐式类型转换在以...

  • 2020-02-19 JS数据类型-隐式转换

    隐式转换 对象的隐式转换var a = { valueOf() { return 1; }, toSt...

  • 第28课:Scala隐式转换内幕实践解密

    其实隐式转换有几种类型:隐式参数,隐式转换,隐式对象,和隐式类 首先看一下Scala的作用域 隐式参数冲突的情况:...

  • scala学习 - 隐式转换和隐式参数

    本文来自《Programming in Scala》一书 scala学习之隐式转换和隐式参数 1 隐式类型转换 ...

网友评论

    本文标题:【进阶】隐式转换

    本文链接:https://www.haomeiwen.com/subject/dxktmqtx.html