笔记:JavaScript中的30个疑难杂症
JavaScript中的30个疑难杂症
目录
- 数据类型
- 表达式运算符和分支结构
- 内置对象
- JS DOM
- JS BOM
- 函数对象
- 面向对象
typeof 和 instanceof
JS数据类型:
- 原始类型(基本类型)Undefined Null Boolean Number String
- 引用类型(复杂类型)Object
1、typeof检测返回对应数据类型
console.log(typeof 123); // number
console.log(typeof true); // boolean
console.log(typeof "hello"); // string
console.log(typeof undefined); // undefined
console.log(typeof null); // object
// 计算机typeof 按照机器码后3位判断数据类型
// null 00000000 => object后3位为0console.log(typeof []); // object
console.log(typeof {}); // object
console.log(typeof new Date()); // object
console.log(typeof function () {}); // function
console.log(typeof Array); // function
// typeof 引用类型 object function
// object + call方法 => function
字符串示例
var name1 = "Tom";
console.log(name1); // Tom
console.log(typeof name1); //stringvar name2 = new String("Tom");
console.log(name2);
// String {"Tom"}
// 0: "T"
// 1: "o"
// 2: "m"
// length: 3
console.log(typeof name2); // object
总结:
typeof 少null, 多function
2、instanceof检测返回bool: true/false
console.log([] instanceof Array); // true
console.log({} instanceof Object); // true
console.log(new Date() instanceof Date); // true
function Person() {}
console.log(new Person() instanceof Person); // trueconsole.log([] instanceof Object); // true
console.log(new Date() instanceof Object); // true
console.log(new Person() instanceof Object); // true
instanceof 原型链查找
A instanceof B == True
B instanceof C == True=> A instanceof C == True
console.log(Object.prototype.toString.call("1")); // [object String]
console.log(Object.prototype.toString.call([])); // [object Array]
总结:
- typeof 返回值是一个字符串:
- number string boolean
- function(函数)
- object(null,数组,对象)
- undefined
- instanceof 返回布尔值,判断A 是否为B 的实例对象,检测的是原型
数据的存储形式-堆栈
js堆栈
- 栈:计算机为原始类型开辟的一块内存空间 string number…
- 堆:计算机为引用类型开辟的一块内存空间 object
// 栈中存放数值
var a = "Tom";
var b = a;
b = "Jack";
console.log(a, b);
// Tom Jack// 栈中存放地址值,堆中存放对象
var c = { key: "value" };
var d = c;
d.key = "newValue";
console.log(c, d);
// {key: "newValue"} {key: "newValue"}
深拷贝和浅拷贝
- 深拷贝:修改复制对象,原始对象不会变化
- 浅拷贝:修改复制对象,原始对象也变化
方式:
- 遍历赋值
- Object.create()
- JSON.parse()和JSON.stringify()
操作的对象
var obj = {a: "Hello",b: {a: "world",b: 111,},c: [11, "Jack", "Tom"],
};
1、浅拷贝
1-1、遍历赋值
// 浅拷贝
function simpleCopy(o) {var o_ = {};for (let i in o) {o_[i] = o[i];}return o_;
}var newObj = simpleCopy(obj);
newObj.b.a = "WORLD";
console.log(obj);console.log(newObj);/**
obj 和 newObj都变了:
b: { "a": "WORLD", "b": 111}}
*/
1-2、Object.create()
var newObj = Object.create(obj);
newObj.b.a = "WORLD";console.log(obj);
// b: {a: "WORLD", b: 111}
console.log(newObj);
// __proto__:
// b: {a: "WORLD", b: 111}
2、深拷贝
2-1、遍历赋值
function deepCopy(object, deepObject=null) {let deepObj = deepObject || {};for (let i in object) {if (typeof object[i] === "object") {// 引用类型 [] {} nullif(object[i] === null){deepObj[i] = object[i];} else{deepObj[i] = object[i].constructor === Array ? []: {}// deepObj[i] = object[i].constructor === Array ? []: Object.create(null);deepCopy(object[i], deepObj[i])}} else{// 简单数据类型deepObj[i] = object[i];}}return deepObj;
}var newObj = deepCopy(obj);
newObj.b.a = "WORLD";console.log(obj);
// b: {a: "world", b: 111}
console.log(newObj);
// b: {a: "WORLD", b: 111}
2-2 JSON
function deepCopy(o) {return JSON.parse(JSON.stringify(o));
}var newObj = deepCopy(obj);
newObj.b.a = "WORLD";console.log(obj);
// b: {a: "world", b: 111}
console.log(newObj);
// b: {a: "WORLD", b: 111}
数据类型转换
1、 特殊类型的隐式转换 NaN, 0, undefined, null, “” => false
Boolean(NaN) // falseBoolean(null) // falseBoolean(undefined) // falseBoolean(0) // falseBoolean("") // falseBoolean([]) // trueBoolean({}) // true
2、 逻辑运算符&&
和 ||
console.log(true && true) // trueconsole.log(false || false) // falseconsole.log(5 || 0) // 5console.log(0 || 5) // 5
运用
var a = 0;if(a === 0){console.log(a);
} else{console.log(5);
}// 等价于
console.log(a && 5);
3、 == 和 ===
== 比较值
=== 比较值 和 类型
console.log(undefined == null); // true
console.log(undefined === null); // falseconsole.log(0 == '0'); // true
console.log(0 === '0'); // false
多运算符和分支结构
1、运算符的优先级
+-/* && || () -= -- ++增加括号
2、舍入误差
console.log(1.0 + 2.0)
// 3console.log(0.1 + 0.2)
// 0.30000000000000004// 转二进制
(0.1).toString(2)
"0.0001100110011001100110011001100110011001100110011001101"
舍入误差解决
// 方案一: 舍去后面的位数
console.log(parseFloat((0.1 + 0.2).toFixed(2)));
// 0.3// 方案二
function add(num1 , num2){let n = Math.pow(10, 2); // 增大一定倍数,使得两个数进行整数计算return ((num1 * n) + (num2 * n)) / n
}console.log(add(0.1, 0.2));
// 0.3
优化for循环
性能优化
var array = [];for (let index = 0; index < array.length; index++) {// do something
}// 优化后
for (let index = 0, len = array.length; index < len; index++) {// do something
}
算法优化
// 求和:1 + 2 + 3 + 4 +... + 100
var sum = 0;
for (let i = 1; i <= 100; i++) {sum += i;
}
console.log(sum); // 5050// 等差数列公式求和公式 Sn=n(a1+an)/2
console.log(((1 + 100) * 100) / 2); // 5050
例题:找出两个数,和为11,返回下标
var list = [1, 7, 3, 4, 5, 6];
方式一:
var loop = 0;for (let i = 0; i < list.length; i++) {for (let j = 0; j < list.length; j++) {if (list[i] + list[j] == 11) {console.log(i, j);// 1 3, 3 1, 4 5, 5 4}console.log("loop", ++loop);// loop 36}
}
方式二:
var loop = 0;
for (let i = 0; i < list.length; i++) {let index = list.indexOf(11 - list[i]);if (index > -1) {console.log(i, index);// 1 3, 3 1, 4 5, 5 4}console.log("loop", ++loop);// loop 6
}
js中常见的内置对象
1、三种包装对象:String, Number, Boolean
// 内置对象调用方法
var str = "Hello";var str_ = new String('Hello')
str = str_.toUpperCase()
str_ = nullstr.toUpperCase()
火狐浏览器可以打印出对象的方法
console.log(Number)
console.log(String)
console.log(Boolean)
2、其他标准内置对象:Array, Date, Function, Object…
装箱和拆箱
- 装箱:基本数据类型 -> 引用数据类型
var num = 123;
var numObj = new Number(123);console.log(typeof num) // number
console.log(typeof numObj) // object
- 拆箱:引用数据类型 -> 基本数据类型
var numObj = new Number(123);console.log(numObj.valueOf()) // 123console.log(typeof numObj.valueOf()) // number
拆箱操作原理:
内部执行toPrimitive(input, type)
input 传入的值
type 值类型1. 如果是原始类型的值直接返回
2. 如果不是,调用 input.valueOf() 是原始类型就返回
3. 如果不是,继续调用 input.toString() 是原始类型就返回
4. 报错
valueOf() 有原始类型的值返回,没有返回对象本身
toString() 对象[object type] type:对象类型
例题1:
console.log([] + [])
<empty string>// 分析:
console.log([])
Array []console.log([].valueOf())
Array []console.log([].toString())
<empty string>
例题2:
console.log([] + {})
[object Object]// 分析:
console.log({}.valueOf())
{}console.log({}.toString())
[object Object]// 交换位置,{}可能被识别为代码块
console.log({} + [])
[object Object] 或 0console.log(+ [])
0console.log(+ '')
0console.log(+ {})
NaN
深入理解栈和队列
栈: 后进先出 LIFO (last in first out)
队列: 先进先出 FIFO (first in first out)
栈和堆:数据存储
栈和队列:数据访问顺序
js数组 具备了 栈 + 队列
push
pop
unshift
shift
var list = [1, 2, 3];// 队尾入栈
list.push(4);
console.log(list); // [ 1, 2, 3, 4 ]var val1 = list.pop();
console.log(list); // [ 1, 2, 3 ]
console.log(val1); // 4// 队首入栈
list.unshift(0);
console.log(list); // [0, 1, 2, 3]var val2 = list.shift();
console.log(list); // [1, 2, 3]
console.log(val2); // 0
- 结尾出入栈,不影响原有数据位置索引,效率高
- 开头出入栈,会影响原有的数据位置索引,效率低
sort列表排序
var list1 = [1, 3, 2, 5, 8];
console.log(list1.sort()); // [1, 2, 3, 5, 8]// 得到不期望的排序结果
var list2 = [3, 23, 15, 9, 31];
console.log(list2.sort()); // [15, 23, 3, 31, 9]
sort:
- 默认升序
- 按照字符串Unicode码进行排序
解决
定义一个比较器函数
function compare(x, y) {return x - y;
}var list1 = [1, 3, 2, 5, 8];
console.log(list1.sort(compare)); // [1, 2, 3, 5, 8]// 得到期望的排序结果
var list2 = [3, 23, 15, 9, 31];
console.log(list2.sort(compare)); // [3, 9, 15, 23, 31]
Date对象中的getMonth()
// 2021-03-15 星期一
var now = new Date();
console.log(now.getTime()); // 13位时间戳,1970年1月1日至今的毫秒数
// 单位是毫秒: 1615820418925console.log(now.getDate()); // 本月第几号: 15
console.log(now.getDay()); // 本周第几天1-7:1
console.log(now.getMonth() + 1); // 月份:3
客户端的时间可以修改
严谨的时间需要后端给
开发中的编码和解码
- escape/unescape
- encodeURI/decodeURI
- encodeURIComponent/decodeURIComponent
1、escape/unescape
处理ASCII码表之外的字符
var url = "http://www.baidu.com?name=张三&age=23";
console.log(escape(url));
// http%3A//www.baidu.com%3Fname%3D%u5F20%u4E09%26age%3D23var escapeUrl = "http%3A//www.baidu.com%3Fname%3D%u5F20%u4E09%26age%3D23";
console.log(unescape(escapeUrl));
// http://www.baidu.com?name=张三&age=23
2、encodeURI/decodeURI(用的较多)
处理unicode编码
var url = "http://www.baidu.com?name=张三&age=23";
console.log(encodeURI(url));
// http://www.baidu.com?name=%E5%BC%A0%E4%B8%89&age=23var escapeUrl = "http://www.baidu.com?name=%E5%BC%A0%E4%B8%89&age=23";
console.log(decodeURI(escapeUrl));
// http://www.baidu.com?name=张三&age=23
3、encodeURIComponent/decodeURIComponent
var url = "http://www.baidu.com?name=张三&age=23";
console.log(encodeURIComponent(url));
// http%3A%2F%2Fwww.baidu.com%3Fname%3D%E5%BC%A0%E4%B8%89%26age%3D23var escapeUrl = "http%3A%2F%2Fwww.baidu.com%3Fname%3D%E5%BC%A0%E4%B8%89%26age%3D23";
console.log(decodeURIComponent(escapeUrl));
// http://www.baidu.com?name=张三&age=23
理解DOM树的加载过程
浏览器发起请求过程
浏览器URL -> DNS域名解析 -> IP所在服务器发起请求
浏览器处理响应过程
html:二进制转为html构建DOM树:Html解析: Token -> Node -> DOMToken词法解析: 根是document对象 <div></div>Node:HTML div ElementDOM: DOM和标签是一一对应的关系解析过程中:link css并行下载script 先执行js,完成后继续构建DOM树底部引入js 头部引入js, 加async,deferasync: 异步下载js文件,不影响DOM解析,下载完成后尽快执行jsdefer:文档渲染完后,DOMContentLoaded时间调用之前,按照顺序执行jswindown.onload构建css树:CSS解析器每个css文件解析为CSSStyleSheet样式表对象,每个对象都包含CSSRule, CSSRule包含选择器和声明对象Token解析->Node->CSSOM构建Render树:渲染树 = DOM树 + CSS树布局layout和绘制paint: 计算对象之间的大小,确定每个节点在屏幕上的坐标映射浏览器屏幕绘制,使用UI后端层绘制每个节点reflow 回流:元素属性发生变化且影响布局时(高、宽、内外边距等)相当于刷新页面repaint 重绘:元素属性发生变化且不影响布局时(颜色、透明度、字体样式等)相当于不刷新页面,动态更新内容重绘不一定引起回流,回流必将引起重绘
3种事件绑定的异同
html事件
dom0事件
dom2事件
- 广义javascript ECMAScript + DOM + BOM DOM0 DOM1 DOM2
- 狭义javascript ECMAScript ES6 ES5 ES3
事件监听的优点:可以绑定多个事件,常规的事件绑定只执行最后绑定的事件
事件绑定:相当于存储了函数地址,再绑定一个事件,相当于变量指向了另一个函数地址
事件监听:相当于订阅发布者,改变了数据,触发了事件,订阅这个事件的函数被执行
addEventListener函数
element.addEventListener(event, function, useCapture)
removeEventListener()event (必需)事件名
function (必需)事件触发函数
useCapture (可选)指定事件在捕获(tru)或冒泡(false)阶段执行IE8: element.attathEvent(event, function)event (必需)事件名, 需加'on' eg: onclick
function (必需)事件触发函数
<button onclick="func1()">Html事件</button>
<button id="btn0">事件绑定</button>
<button id="btn2">事件监听</button><script>function func1() {console.log("func1");}function func2() {console.log("func2");}function func3() {console.log("func3");}function func4() {console.log("func4");}function func5() {console.log("func5");}// dom0级事件:事件绑定; 只执行func3document.getElementById("btn0").onclick = func2;document.getElementById("btn0").onclick = func3;// dom2级事件:事件监听; 两个函数都会执行document.getElementById("btn2").addEventListener("click", func4);document.getElementById("btn2").addEventListener("click", func5);
</script>
事件触发、事件捕获与事件冒泡
事件捕获与事件冒泡
向下是捕获阶段
---------------| ^
---------------V ^
---------------V |
---------------
向上是冒泡阶段
事件对象:
事件触发时包含了事件发生的元素和属性信息
var div3 = document.getElementById("div3");
div3.addEventListener("click", function (e) {var e = e || window.event; // IE 8 window.event arguments[0]console.log(e);
}, false); // true: 捕获, false: 冒泡(默认)
事件的周期
--------------------
div1 |
--------------- |
div2 | |
-------- | |
div3 | | |
-------- | |
--------------- |
--------------------
<style>
#div1 {width: 300px;height: 300px;background-color: green;
}#div2 {width: 200px;height: 200px;background-color: blue;
}#div3 {width: 100px;height: 100px;background-color: grey;
}
</style><div id="div1">div1<div id="div2">div2<div id="div3">div3</div></div>
</div><script>
// 事件对象:时间触发时包含了事件发生的元素和属性信息
var div3 = document.getElementById("div3");
div3.addEventListener("click",function (e) {console.log("div3");},false
);var div2 = document.getElementById("div2");
div2.addEventListener("click",function (e) {console.log("div2");},false
);var div1 = document.getElementById("div1");
div1.addEventListener("click",function (e) {console.log("div1");},false
);/**
* 点击div 3
*
* div3 -> div2 -> div1
*/
</script>
阻止冒泡:
e.stopPropagation()e.cancelBubble = true // IE8
事件冒泡的应用:事件委托
<div id="demo"><li>aaaaaa</li><li>bbbbbb</li><li>cccccc</li>
</div><script>// 事件委托var demo = document.getElementById("demo");demo.addEventListener("click", function (e) {if (e.target.nodeName.toLowerCase() == "li") {console.log(e.target.innerHTML);}}, false );</script>
阻止默认行为的两种方式
- e.preventDefault()
- return false
让a标签链接不跳转
<a href="https://www.baidu.com/">百度</a><script>var a = document.querySelector('a')a.onclick = function(e){// 方式一// e.preventDefault()// 方式二return false;}
</script>
让form表单不提交
<form action="/post"><input type="submit" value="提交" id="submit"/>
</form><script>var submit = document.getElementById("submit");submit.onclick = function (e) {// 方式一// e.preventDefault()// 方式二return false;};
</script>
使用History和location
1、History
window.hostory 属性指向History对象
表示当前窗口的浏览历史
类似栈的数据结构
History:back()forward()go() 0 -1 -2pushState()replaceState()
2、Location
window.location和document.location
Location:href: 整个URLprotocal URL协议,包括冒号host 主机 包括主机名,冒号:,端口hostname 主机名,不包括端口port 端口号pathname URL的路径部分,从根路径/ 开始search 查询字符串,问号?开始hash 片段字符串 从#开始username 用户名password 密码origin URL协议,主机名,端口号
常见函数的4种类型
- 匿名函数
- 回调函数
- 递归函数
- 构造函数
1、匿名函数
定义时候没有任何变量引用的函数
匿名函数自调:函数只执行一次
(function(a, b){console.log(a + b);}
)(1, 2);// 等价于
function foo (a, b){console.log(a + b);
}foo(1, 2);
jQuery:
(function(window, undefined){var jQuery;...window.jQuery = window.$ = jQuery;
})(window);
优点:节约内存空间,掉用前和调用后内存中不创建任何函数对象
2、回调函数callback
如果一个函数作为对象交给其他函数使用
var arr = [33, 9, 11, 6];arr.sort(function (a, b) {return a - b;
});console.log(arr);
// [6, 9, 11, 33]
异步回调
function getPrice(params, callback){$.ajax({url: '/getPrice',type: 'POST',data: params,success: function(data){callback(data);}})
}
3、递归函数
循环调用函数本身
var func = function(x) {if(x === 2){return x} else{return x * f(x - 1)}
}
arguments.callee 严格模式下不支持使用 use strict
function func(x){if(x === 1){return 1} else{return x * arguments.callee(x -1)}
}
4、构造函数
构造函数习惯上首字母大写
调用方式不一样,作用也不一样
构造函数用来新建实例对象
Person 既是函数名,也是这个对象的类名
function Person(){} // 构造函数new Person()function person(){} // 方法
变量和函数提升
- js解释执行
- 变量和函数提升
变量声明提前,函数声明提前
- 变量声明提前:值停留在本地
- 函数声明提前:整个函数体提前
如果是var赋值声明的函数,变量提前,函数体停留在本地
1、变量提升
未声明使用会报错
console.log(a); // Error: a is not defined
var会变量提升
console.log(a); // undefined
var a = 10;
let定义不会提升
console.log(a); // Error: Cannot access 'a' before initialization
let a = 10;
2、函数提升
console.log(func); // func(){}
function func(){}
console.log(foo); // undefined
var foo = function func(){}
console.log(func); // Error: func is not defined
var foo = function func(){}
作用域和作用域链
全局作用域,函数作用域
作用域链
作用域scope: 一个变量的可用范围
作用域链scope chain:以当前作用域的scope属性为起点,依次引用每个AO,直到window结束,行成多级引用关系
js作用域ES5
- 全局作用域 window
- 函数作用域 function(){}
js的变量和函数作用域是在定义是决定的,而不是运行时决定
js的变量作用域在函数体内有效,无块作用域
作用域示例
function a() {function b() {var bb = "bb";}b()var aa = "aa";
}a()console.log(bb); // Error: bb is not defined
console.log(aa); // Error: aa is not defined
[[scope]]
作用域链
var cc = 'cc'function a() {function b() {var bb = "bb";console.log(aa); // undefinedconsole.log(cc); // cc}b()var aa = "aa";console.log(cc); // cc
}a()
面试题
var buttons = [{name: 'n1'}, {name: 'n2'}, {name: 'n3'}]function bind(){for(var i = 0; i < buttons.length; i++){buttons[i].func = function(){console.log(i);}}
}bind()buttons[0].func() // 3
buttons[1].func() // 3
buttons[2].func() // 3
function bind(){for(var i =0; i < buttons.length; i++){let num = i;buttons[i].func = function(){console.log(num);}}
}
// 输出 0 1 2
执行环境
浏览器环境栈:js是一个单线程程序
执行环境(执行上下文):EC execution context
全局执行环境
局部执行环境
变量对象:VO variable object 保存全局环境下变量的对象
活动对象:AO activation object 保存函数环境中的变量
重载和多态的使用场景
重载:定义相同名称,不同参数的函数,程序调用时自动识别不同参数的函数
实现了相同函数名不同的函数调用
js中没有重载,可以通过arguments实现函数重载
/*** 计算正方形或长方形面积*/
function React() {if (arguments.length == 1) {// 如果是1个参数,返回正方形this.width = arguments[0];this.height = arguments[0];} else {// 如果是2个参数,返回长方形this.width = arguments[0];this.height = arguments[1];}this.toString = function () {return `${this.width} * ${this.height} = ${this.width * this.height}`;};
}var react1 = new React(5);
console.log(react1.toString());
// 5 * 5 = 25var react2 = new React(3, 4);
console.log(react2.toString());
// 3 * 4 = 12
多态:同一个东西在不同情况下表现不同状态,重写和重载
闭包
闭包的概念Closure:作用域
引用了自由变量的函数,这个被引用的自由变量将和这个函数一同存在;
即使已经离开了创造它的环境也不例外。
所以,闭包是由函数和其他相关的引用环境组合而成,实现信息驻留;
信息的保存,引用在,空间不销毁
简单的使用
var Person = function () {var count = 0;return function () {return count++;};
};var p = Person()
console.log(p()); // 0
console.log(p()); // 1
console.log(p()); // 2
闭包的应用
var buttons = [{name: 'n1'}, {name: 'n2'}, {name: 'n3'}]function bind() {for (var i = 0; i < buttons.length; i++) {// 定义一个立即执行函数,行成闭包(function (num) {buttons[i].func = function () {console.log(num);};})(i);}
}bind();buttons[0].func(); // 0
buttons[1].func(); // 1
buttons[2].func(); // 2
闭包缺点:
闭包导致内存驻留,如果是大量对象的闭包环境需要注意内存消耗
ES6中使用let定义局部变量也可以实现输出0 1 2
function bind() {for (let i = 0; i < buttons.length; i++) {buttons[i].func = function () {console.log(i);};}
}
call、apply、bind的使用场景区分
call、apply、bind都是Function对象的方法
1、apply调用一个函数,可以指定this值
Function.apply(obj, args)obj: 这个对象将替代Function类里的this对象
args: 是一个数组
2、call
Function.call(obj, ...args)
args: 单个参数
var stu1 = {name: "Tom",say: function (age, school) {console.log(this.name, age, school);},
};var stu2 = {name: "Jack",
}stu1.say(18, '清华'); // Tom 18 清华
stu1.say.call(stu2, 28, '北大'); // Jack 28 北大
stu1.say.apply(stu2, [28, '北大']); // Jack 28 北大
类数组转数组
var arr = Array.prototype.slice.apply(arguments)
3、bind:
类似call, 不同之处在于call调用之后立即执行,bind需要一个变量进行接收之后再执行
new的执行过程
var Person = function(name, age){this.name = name;this.age = age;
}var person =new Person('Tom', 23);
console.log(person.name); // Tom// 分4步
// 1、创建一个新对象obj
var obj = new Object();// 2、把obj的proto指向构造函数的prototype对象,实现继承
obj.__proto__ = Fn.prototype;// 3、将this指向obj
var result = Fn.call(obj)// 4、返回创建的obj,如果该函数没有返回对象,则返回this
if(typeof result === 'object'){return result; // func = result
} else{return obj; // func = obj
}
this的使用
this指向
指代当前调用的这个对象
4中绑定(优先级从低到高):
- 默认绑定
- 隐式绑定
- 显示绑定
- new绑定
var person = {name: 'Tom',age: 23,showName: function () {// this->personconsole.log(this.name); // Tom},showAge: function () {// 局部函数function _age(){// this->windowconsole.log(this.age); // undefined}_age();// this->personconsole.log(this.age); // 23},
};person.showName()
person.showAge()
可以先保存this
let that = this;
改变this指向
call/apply/bind
var name = 'Tom'var person = {name: 'Jack',showName: function(){console.log(this.name);}
}person.showName(); // Jack// this->window
var show = person.showName;
show(); // Tomvar fn = person.showName.bind(person);
fn(); // Jack
实现一个bind方法
Function.prototype.bind = function(obj){var that = this;return function(){that.apply(obj)}
}// 验证
var name = 'Tom'var person = {name: 'Jack',showName: function(){console.log(this.name);}
}var fn = person.showName.bind(person);
fn(); // Jack
理解面向对象
对象:
- 具备私有属性 {name: ‘Tom’}
- 只要是new出来的都是对象 new Func() => 实例化
- 不同对象可定不相等
- 对象都会有引用机制
js万物皆对象
- 字面量 - 字面显示的内容 Array Date Object
- 包装类 - 没有new的函数声明,可以理解为不是对象 String, NUmber
面向对象:
把任何的数据和行为抽象成一个形象的对象
面向对象OOP:继承 封装 多态
java: classjs: function Person(){}; new Person()
- 继承:子继承父
- 封装:方法function()
- 多态: 重载、重写(继承)
原型和原型链
创建对象
// 1、函数对象
var func = new Function("str", "console.log(str)");
func("hi"); // hi// 2、普通对象
var obj1 = {name: "Tom",getName: function () {return this.name;},
};
console.log(obj1.getName()); // Tomvar obj2 = new Object();// 3、构造函数创建对象
function Person(name) {this.name = name;this.getName = function () {return this.name;};
}var p1 = new Person('Tom');
var p2 = new Person('Jack');console.log(p1.getName()); // Tom
console.log(p2.getName()); // Jack
原型和原型链
- 一句话:万物皆对象,万物皆空null
- 两个定义:
- 原型:保存所有子对象的公有属性值和方法的父对象
- 原型链:由各级子对象的
__proto__
属性连续引用行成的结构
- 三个属性:
__proto__
、constructor、prototype
// 构造函数实现类
function Person(name, age) {this.name = name;this.age = age;this.say = function () {console.log(this.name + this.age);};
}// 1、当函数创建的时候就会携带prototype属性,指向原型对象
Person.prototype.money = 20;Person.prototype.run = function(){console.log('run...');
}// constructor
console.log(Person.prototype.constructor === Person); // truevar p1 = new Person("Tom", 18);// 所有对象都会携带 __proto__
console.log(p1.__proto__ === Person.prototype); // true
- 挂载在函数内部的方法上,实例化对象内部会复制构造函数的方法
- 挂载在原型上的方法,不会复制
- 挂载在内部和原型上的方法都是可以通过实例去调用的
- 一般来说,如果需要访问构造函数内部的私有变量,我们可以定义再函数内部;
其他情况可以定义再函数的原型上
总结
- 所有对象都携带
obj.__proto__
obj.__proto__ === Person.prototype
Person.prototype.constructor === Person
原型相关API
Function和Object关系
function Person(){}
var person = new Person()=>
person.__proto__ -> Person.prototype
Person.prototype.constructor -> PersonFunction.__proto__ -> Function.prototypePerson.__proto__ -> Function.prototype
Object.__proto__ -> Function.prototype
Function.prototype.__proto__ -> Object.prototype
Object.prototype.__proto__ -> null翻一下:
令:
__class__ = __proto__
Person[class] = Person.prototypeperson.__class__ -> Person[class]
Person[class].constructor -> PersonFunction.__class__ -> Function[class] // 特殊Person.__class__ -> Function[class]
Object.__class__ -> Function[class] // 特殊
Function[class].__class__ -> Object[class]
Object[class].__class__ -> null就是一个实例找类的过程,有特殊
Function对象和Object对象之间的关系
- Function是顶层的构造器,Object是顶层的对象
- 顶层有null, Object.prototype, Function.prototype Function
- 原型上说:Function继承了Object
- 构造器上说:Function构造了Object
原型相关API判断对象的属性是自有还是私有
- hasOwnProperty
- isPropertyOf 判断对象是否在原型链中
- getPropertyOf 获取原型对象的标准方法
继承
继承的方式
继承的6种方式
- 简单原型链:类式继承
- 借用构造函数:缺点=> 父类的原型方法自然不会被子类继承
- 组合继承(最常用):类式继承+构造函数式继承
- 寄生组合继承(最佳方式):寄生式继承+构造函数式继承
- 原型式:跟类式继承一样,父对象Book中的值类型的属性被复制,引用类型的属性被共有
- 寄生式:通过在一个函数内的过渡对象实现继承并返回新对象的方式
继承的应用
Object.defineProperty
定义
Object.defineProperty(obj, prop, descriptor)/*
obj:需要定义属性的对象
prop:需要定义的属性
descriptor:属性的描述描述符
返回值:返回此对象
*/
var obj = {}// 数据描述符
var descriptor = {// 能否delete删除,configurable: false,// 是否可写,默认false, 不能被赋值,只读writable: false,// 是否可枚举,即是否可以for...in访问属性,默认falseenumerable: false,// 属性值,默认undefinedvalue: 'hello',// 访问器描述符,不能与数据描述符同时使用// get: 读取,默认undefined// set: 设置,默认undefined
}Object.defineProperty(obj, 'name', descriptor)
console.log(obj.name)
示例:数据响应式 vue
function defineReactive(obj, key, val) {// val,由于闭包的存在,不会被销毁Object.defineProperty(obj, key, {get() {console.log('get');return val;},set(newVal) {if (newVal != val) {console.log('set');val = newVal;}},});
}var obj = {};
defineReactive(obj, 'foo', '123')
console.log(obj.foo); // get 123obj.foo = '223' // set
console.log(obj.foo); // get 223
笔记:JavaScript中的30个疑难杂症相关推荐
- js学习笔记----JavaScript中DOM扩展的那些事
什么都不说,先上总结的图~ Selectors API(选择符API) querySelector()方法 接收一个css选择符,返回与该模式匹配的第一个元素,如果没有找到匹配的元素,返回null. ...
- 小白编程笔记——JavaScript中两种把表单内容传递给Controller的方法
工作的时候看到有两种把页面上内容传递给Controller的方法,其中一种是传递对数据库的搜索条件,并且会根据搜索条件访问数据库,并将更新后的数据写在JqGrid表格里.另一种则是用于为数据库新增数据 ...
- PHP笔记-JavaScript中使用Smarty变量
如果Smarty传来的变量是字符串时: $strData = "abc"; $this->assign("strData", strData); Java ...
- JavaScript学习笔记06【高级——JavaScript中的事件】
w3school 在线教程:https://www.w3school.com.cn JavaScript学习笔记01[基础--简介.基础语法.运算符.特殊语法.流程控制语句][day01] JavaS ...
- JavaScript学习笔记——JS中的变量复制、参数传递和作用域链
今天在看书的过程中,又发现了自己目前对Javascript存在的一个知识模糊点:JS的作用域链,所以就通过查资料看书对作用域链相关的内容进行了学习.今天学习笔记主要有这样几个关键字:变量.参数传递.执 ...
- html5学习笔记---05.JavaScript 中的面向对象,继承和封装
05.JavaScript 中的面向对象 a.创梦技术qq交流群:CreDream:251572072 a.JavaScript 是一种基于对象的语言 类:JavaScript 对象很抽象,所以下 ...
- 【javascript笔记】关于javascript中的闭包
最开始看<javascript高级程序设计>的时候就看到了javascript中的闭包,在第七章第二节....好大概知道了,过了段时间,好了又忘了... 我们来看这本书里面关于闭包是怎么描 ...
- JavaScript 中的内存和性能、模拟事件(读书笔记思维导图)
由于事件处理程序可以为现代 Web 应用程序提供交互能力,因此许多开发人员会不分青红皂白地向页面中添加大量的处理程序.在 JavaScript 中,添加到页面上的事件处理程序数量将直接关系到页面的整体 ...
- JavaScript 中的继承(读书笔记思维导图)
继承是 OO 语言中的一个最为人津津乐道的概念.许多 OO 语言都支持两种继承方式:接口继承和实现继承.接口继承只继承方法签名,而实现继承则继承实际的方法.由于函数没有签名,在 ECMAScript ...
最新文章
- android studio 开发环境搭建
- Bitcoin.com推出BCH新图表,加大对BCH的支持
- 使用注解实现ssh整合
- 格式化 SQL 来提高效率
- 图像超分辨率进ASC19超算大赛,PyTorch+GAN受关注
- Python Imaging Library: ImageFilter Module(图像滤波模块)
- 《量化交易核心策略开发:从建模到实战》读书笔记
- springmvc web.xml和application.xml配置详情(附:完整版pom.xml)
- windows下游戏服务器端框架Firefly安装说明及demo运行
- tomcat乱码的几种解决
- 远程桌面计算机名如何删除,如何删除远程连接记录?如何用电脑识别码实现远程控制?...
- java汉字转拼音maven_java汉字转拼音pinyin4j功能实现示例
- 汉语数字转换成阿拉伯数字
- 公司办公提高无线网络质量解决方案
- C语言:L1-009 N个数求和 (20 分)
- 2012年腾讯实习生笔试附加题
- uniapp editor富文本编辑器,h5富文本编辑器封装成插件
- java 新浪短链接_java高仿新浪微博短链接地址生成工具ShortUrlGenerator.java | 学步园...
- Unity中国象棋(二)——走棋
- 基于python下django框架 实现外卖点餐系统详细设计
热门文章
- 从有线通信到无线通信
- VMM安装freenas
- ai皮肤检测分数_上线俩月,完成2300多万次皮肤测试;找出皮肤问题?AI测肤技术准确率达95%...
- Windows 2008 R2上MySQL5.7异常关闭,报错:mysqld got exception 0xc000001d问题解决
- C++ mutable关键字
- ps cc2018启动界面无响应解决方案
- java 实现在线播放_java文档在线播放实现
- password authentication failed for user “##““
- 一起来创建一个无向图吧!
- 虚幻4渲染编程(灯光篇)【第一卷:各种ShadowMap】